chore: code formatting
This commit is contained in:
54
.github/scripts/build.mjs
vendored
54
.github/scripts/build.mjs
vendored
@@ -1,7 +1,7 @@
|
|||||||
//Imports
|
//Imports
|
||||||
import fs from "fs/promises"
|
|
||||||
import ejs from "ejs"
|
import ejs from "ejs"
|
||||||
import fss from "fs"
|
import fss from "fs"
|
||||||
|
import fs from "fs/promises"
|
||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml"
|
||||||
import paths from "path"
|
import paths from "path"
|
||||||
import sgit from "simple-git"
|
import sgit from "simple-git"
|
||||||
@@ -26,13 +26,13 @@ const __test_secrets = paths.join(paths.join(__metrics, "tests/secrets.json"))
|
|||||||
//Git setup
|
//Git setup
|
||||||
const git = sgit(__metrics)
|
const git = sgit(__metrics)
|
||||||
const staged = new Set()
|
const staged = new Set()
|
||||||
const secrets = Object.assign(JSON.parse(`${await fs.readFile(__test_secrets)}`), { $regex: /\$\{\{\s*secrets\.(?<secret>\w+)\s*\}\}/ })
|
const secrets = Object.assign(JSON.parse(`${await fs.readFile(__test_secrets)}`), {$regex: /\$\{\{\s*secrets\.(?<secret>\w+)\s*\}\}/})
|
||||||
const { plugins, templates } = await metadata({ log: false, diff: true })
|
const {plugins, templates} = await metadata({log: false, diff: true})
|
||||||
const workflow = []
|
const workflow = []
|
||||||
|
|
||||||
//Plugins
|
//Plugins
|
||||||
for (const id of Object.keys(plugins)) {
|
for (const id of Object.keys(plugins)) {
|
||||||
const { examples, options, readme, tests, header, community } = await plugin(id)
|
const {examples, options, readme, tests, header, community} = await plugin(id)
|
||||||
|
|
||||||
//Readme
|
//Readme
|
||||||
console.log(`Generating source/plugins/${community ? "community/" : ""}${id}/README.md`)
|
console.log(`Generating source/plugins/${community ? "community/" : ""}${id}/README.md`)
|
||||||
@@ -40,7 +40,7 @@ for (const id of Object.keys(plugins)) {
|
|||||||
readme.path,
|
readme.path,
|
||||||
readme.content
|
readme.content
|
||||||
.replace(/(<!--header-->)[\s\S]*(<!--\/header-->)/g, `$1\n${header}\n$2`)
|
.replace(/(<!--header-->)[\s\S]*(<!--\/header-->)/g, `$1\n${header}\n$2`)
|
||||||
.replace(/(<!--examples-->)[\s\S]*(<!--\/examples-->)/g, `$1\n${examples.map(({ test, prod, ...step }) => ["```yaml", yaml.dump(step, { quotingType: '"', noCompatMode: true }), "```"].join("\n")).join("\n")}\n$2`)
|
.replace(/(<!--examples-->)[\s\S]*(<!--\/examples-->)/g, `$1\n${examples.map(({test, prod, ...step}) => ["```yaml", yaml.dump(step, {quotingType: '"', noCompatMode: true}), "```"].join("\n")).join("\n")}\n$2`)
|
||||||
.replace(/(<!--options-->)[\s\S]*(<!--\/options-->)/g, `$1\n${options}\n$2`),
|
.replace(/(<!--options-->)[\s\S]*(<!--\/options-->)/g, `$1\n${options}\n$2`),
|
||||||
)
|
)
|
||||||
staged.add(readme.path)
|
staged.add(readme.path)
|
||||||
@@ -54,7 +54,7 @@ for (const id of Object.keys(plugins)) {
|
|||||||
|
|
||||||
//Templates
|
//Templates
|
||||||
for (const id of Object.keys(templates)) {
|
for (const id of Object.keys(templates)) {
|
||||||
const { examples, readme, tests, header } = await template(id)
|
const {examples, readme, tests, header} = await template(id)
|
||||||
|
|
||||||
//Readme
|
//Readme
|
||||||
console.log(`Generating source/templates/${id}/README.md`)
|
console.log(`Generating source/templates/${id}/README.md`)
|
||||||
@@ -62,14 +62,14 @@ for (const id of Object.keys(templates)) {
|
|||||||
readme.path,
|
readme.path,
|
||||||
readme.content
|
readme.content
|
||||||
.replace(/(<!--header-->)[\s\S]*(<!--\/header-->)/g, `$1\n${header}\n$2`)
|
.replace(/(<!--header-->)[\s\S]*(<!--\/header-->)/g, `$1\n${header}\n$2`)
|
||||||
.replace(/(<!--examples-->)[\s\S]*(<!--\/examples-->)/g, `$1\n${examples.map(({ test, prod, ...step }) => ["```yaml", yaml.dump(step, { quotingType: '"', noCompatMode: true }), "```"].join("\n")).join("\n")}\n$2`),
|
.replace(/(<!--examples-->)[\s\S]*(<!--\/examples-->)/g, `$1\n${examples.map(({test, prod, ...step}) => ["```yaml", yaml.dump(step, {quotingType: '"', noCompatMode: true}), "```"].join("\n")).join("\n")}\n$2`),
|
||||||
)
|
)
|
||||||
staged.add(readme.path)
|
staged.add(readme.path)
|
||||||
|
|
||||||
//Tests
|
//Tests
|
||||||
console.log(`Generating tests/templates/${id}.yml`)
|
console.log(`Generating tests/templates/${id}.yml`)
|
||||||
workflow.push(...examples.map(example => testcase(templates[id].name, "prod", example)).filter(t => t))
|
workflow.push(...examples.map(example => testcase(templates[id].name, "prod", example)).filter(t => t))
|
||||||
await fs.writeFile(tests.path, yaml.dump(examples.map(example => testcase(templates[id].name, "test", example)).filter(t => t), { quotingType: '"', noCompatMode: true }))
|
await fs.writeFile(tests.path, yaml.dump(examples.map(example => testcase(templates[id].name, "test", example)).filter(t => t), {quotingType: '"', noCompatMode: true}))
|
||||||
staged.add(tests.path)
|
staged.add(tests.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,21 +77,21 @@ for (const id of Object.keys(templates)) {
|
|||||||
for (const step of ["config", "documentation"]) {
|
for (const step of ["config", "documentation"]) {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case "config":
|
case "config":
|
||||||
await update({ source: paths.join(__action, "action.yml"), output: "action.yml" })
|
await update({source: paths.join(__action, "action.yml"), output: "action.yml"})
|
||||||
await update({ source: paths.join(__web, "settings.example.json"), output: "settings.example.json" })
|
await update({source: paths.join(__web, "settings.example.json"), output: "settings.example.json"})
|
||||||
break
|
break
|
||||||
case "documentation":
|
case "documentation":
|
||||||
await update({ source: paths.join(__documentation, "README.md"), output: "README.md", options: { root: __readme } })
|
await update({source: paths.join(__documentation, "README.md"), output: "README.md", options: {root: __readme}})
|
||||||
await update({ source: paths.join(__documentation, "plugins.md"), output: "source/plugins/README.md" })
|
await update({source: paths.join(__documentation, "plugins.md"), output: "source/plugins/README.md"})
|
||||||
await update({ source: paths.join(__documentation, "plugins.community.md"), output: "source/plugins/community/README.md" })
|
await update({source: paths.join(__documentation, "plugins.community.md"), output: "source/plugins/community/README.md"})
|
||||||
await update({ source: paths.join(__documentation, "templates.md"), output: "source/templates/README.md" })
|
await update({source: paths.join(__documentation, "templates.md"), output: "source/templates/README.md"})
|
||||||
await update({ source: paths.join(__documentation, "compatibility.md"), output: ".github/readme/partials/documentation/compatibility.md" })
|
await update({source: paths.join(__documentation, "compatibility.md"), output: ".github/readme/partials/documentation/compatibility.md"})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Example workflows
|
//Example workflows
|
||||||
await update({ source: paths.join(__metrics, ".github/scripts/files/examples.yml"), output: ".github/workflows/examples.yml", context: { steps: yaml.dump(workflow, { quotingType: '"', noCompatMode: true }) } })
|
await update({source: paths.join(__metrics, ".github/scripts/files/examples.yml"), output: ".github/workflows/examples.yml", context: {steps: yaml.dump(workflow, {quotingType: '"', noCompatMode: true})}})
|
||||||
|
|
||||||
//Commit and push
|
//Commit and push
|
||||||
if (mode === "publish") {
|
if (mode === "publish") {
|
||||||
@@ -109,10 +109,10 @@ console.log("Success!")
|
|||||||
//==================================================================================
|
//==================================================================================
|
||||||
|
|
||||||
//Update generated files
|
//Update generated files
|
||||||
async function update({ source, output, context = {}, options = {} }) {
|
async function update({source, output, context = {}, options = {}}) {
|
||||||
console.log(`Generating ${output}`)
|
console.log(`Generating ${output}`)
|
||||||
const { plugins, templates, packaged, descriptor } = await metadata({ log: false })
|
const {plugins, templates, packaged, descriptor} = await metadata({log: false})
|
||||||
const content = await ejs.renderFile(source, { plugins, templates, packaged, descriptor, ...context }, { async: true, ...options })
|
const content = await ejs.renderFile(source, {plugins, templates, packaged, descriptor, ...context}, {async: true, ...options})
|
||||||
const file = paths.join(__metrics, output)
|
const file = paths.join(__metrics, output)
|
||||||
await fs.writeFile(file, content)
|
await fs.writeFile(file, content)
|
||||||
staged.add(file)
|
staged.add(file)
|
||||||
@@ -160,15 +160,15 @@ async function template(id) {
|
|||||||
|
|
||||||
//Testcase generator
|
//Testcase generator
|
||||||
function testcase(name, env, args) {
|
function testcase(name, env, args) {
|
||||||
const { prod = {}, test = {}, ...step } = JSON.parse(JSON.stringify(args))
|
const {prod = {}, test = {}, ...step} = JSON.parse(JSON.stringify(args))
|
||||||
const context = { prod, test }[env] ?? {}
|
const context = {prod, test}[env] ?? {}
|
||||||
const { with: overrides } = context
|
const {with: overrides} = context
|
||||||
if (context.skip)
|
if (context.skip)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
Object.assign(step.with, context.with ?? {})
|
Object.assign(step.with, context.with ?? {})
|
||||||
delete context.with
|
delete context.with
|
||||||
const result = { ...step, ...context, name: `${name} - ${step.name ?? "(unnamed)"}` }
|
const result = {...step, ...context, name: `${name} - ${step.name ?? "(unnamed)"}`}
|
||||||
for (const [k, v] of Object.entries(result.with)) {
|
for (const [k, v] of Object.entries(result.with)) {
|
||||||
if ((env === "test") && (secrets.$regex.test(v)))
|
if ((env === "test") && (secrets.$regex.test(v)))
|
||||||
result.with[k] = v.replace(secrets.$regex, secrets[v.match(secrets.$regex)?.groups?.secret])
|
result.with[k] = v.replace(secrets.$regex, secrets[v.match(secrets.$regex)?.groups?.secret])
|
||||||
@@ -177,21 +177,21 @@ function testcase(name, env, args) {
|
|||||||
if (env === "prod") {
|
if (env === "prod") {
|
||||||
result.if = "${{ success() || failure() }}"
|
result.if = "${{ success() || failure() }}"
|
||||||
result.uses = "lowlighter/metrics@master"
|
result.uses = "lowlighter/metrics@master"
|
||||||
Object.assign(result.with, { output_action: "none", delay: 120 })
|
Object.assign(result.with, {output_action: "none", delay: 120})
|
||||||
|
|
||||||
for (const { property, value } of [{ property: "user", value: "lowlighter" }, { property: "plugins_errors_fatal", value: "yes" }]) {
|
for (const {property, value} of [{property: "user", value: "lowlighter"}, {property: "plugins_errors_fatal", value: "yes"}]) {
|
||||||
if (!(property in result.with))
|
if (!(property in result.with))
|
||||||
result.with[property] = value
|
result.with[property] = value
|
||||||
}
|
}
|
||||||
if ((overrides?.output_action) && (overrides?.committer_branch === "examples"))
|
if ((overrides?.output_action) && (overrides?.committer_branch === "examples"))
|
||||||
Object.assign(result.with, { output_action: overrides.output_action, committer_branch: "examples" })
|
Object.assign(result.with, {output_action: overrides.output_action, committer_branch: "examples"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env === "test") {
|
if (env === "test") {
|
||||||
if (!result.with.base)
|
if (!result.with.base)
|
||||||
delete result.with.base
|
delete result.with.base
|
||||||
delete result.with.filename
|
delete result.with.filename
|
||||||
Object.assign(result.with, { use_mocked_data: "yes", verify: "yes" })
|
Object.assign(result.with, {use_mocked_data: "yes", verify: "yes"})
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
8
.github/scripts/markdown_example.mjs
vendored
8
.github/scripts/markdown_example.mjs
vendored
@@ -11,11 +11,11 @@ const browser = await puppeteer.launch({
|
|||||||
const page = await browser.newPage()
|
const page = await browser.newPage()
|
||||||
|
|
||||||
//Select markdown example and take screenshoot
|
//Select markdown example and take screenshoot
|
||||||
await page.setViewport({ width: 600, height: 600 })
|
await page.setViewport({width: 600, height: 600})
|
||||||
await page.goto("https://github.com/lowlighter/metrics/blob/examples/metrics.markdown.md")
|
await page.goto("https://github.com/lowlighter/metrics/blob/examples/metrics.markdown.md")
|
||||||
const clip = await page.evaluate(() => {
|
const clip = await page.evaluate(() => {
|
||||||
const { x, y, width, height } = document.querySelector("#readme").getBoundingClientRect()
|
const {x, y, width, height} = document.querySelector("#readme").getBoundingClientRect()
|
||||||
return { x, y, width, height }
|
return {x, y, width, height}
|
||||||
})
|
})
|
||||||
await page.screenshot({ type: "png", path: "/tmp/metrics.markdown.png", clip, omitBackground: true })
|
await page.screenshot({type: "png", path: "/tmp/metrics.markdown.png", clip, omitBackground: true})
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
|||||||
10
.github/scripts/presets_examples.mjs
vendored
10
.github/scripts/presets_examples.mjs
vendored
@@ -1,6 +1,6 @@
|
|||||||
//Imports
|
//Imports
|
||||||
import fs from "fs/promises"
|
|
||||||
import processes from "child_process"
|
import processes from "child_process"
|
||||||
|
import fs from "fs/promises"
|
||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import paths from "path"
|
import paths from "path"
|
||||||
@@ -16,7 +16,7 @@ const __metrics = paths.join(paths.dirname(url.fileURLToPath(import.meta.url)),
|
|||||||
const __presets = paths.join(__metrics, ".presets")
|
const __presets = paths.join(__metrics, ".presets")
|
||||||
|
|
||||||
if ((!await fs.access(__presets).then(_ => true).catch(_ => false)) || (!(await fs.lstat(__presets)).isDirectory()))
|
if ((!await fs.access(__presets).then(_ => true).catch(_ => false)) || (!(await fs.lstat(__presets)).isDirectory()))
|
||||||
await sgit().clone(`https://github-actions[bot]:${process.env.GITHUB_TOKEN}@github.com/lowlighter/metrics`, __presets, { "--branch": "presets", "--single-branch": true })
|
await sgit().clone(`https://github-actions[bot]:${process.env.GITHUB_TOKEN}@github.com/lowlighter/metrics`, __presets, {"--branch": "presets", "--single-branch": true})
|
||||||
const git = sgit(__presets)
|
const git = sgit(__presets)
|
||||||
await git.pull()
|
await git.pull()
|
||||||
const staged = new Set()
|
const staged = new Set()
|
||||||
@@ -27,7 +27,7 @@ web.run = async vars => await fetch(`http://localhost:3000/lowlighter?${new url.
|
|||||||
web.start = async () =>
|
web.start = async () =>
|
||||||
new Promise(solve => {
|
new Promise(solve => {
|
||||||
let stdout = ""
|
let stdout = ""
|
||||||
web.instance = processes.spawn("node", ["source/app/web/index.mjs"], { env: { ...process.env, SANDBOX: true } })
|
web.instance = processes.spawn("node", ["source/app/web/index.mjs"], {env: {...process.env, SANDBOX: true}})
|
||||||
web.instance.stdout.on("data", data => (stdout += data, /Server ready !/.test(stdout) ? solve() : null))
|
web.instance.stdout.on("data", data => (stdout += data, /Server ready !/.test(stdout) ? solve() : null))
|
||||||
web.instance.stderr.on("data", data => console.error(`${data}`))
|
web.instance.stderr.on("data", data => console.error(`${data}`))
|
||||||
})
|
})
|
||||||
@@ -44,13 +44,13 @@ for (const path of await fs.readdir(__presets)) {
|
|||||||
|
|
||||||
//Example
|
//Example
|
||||||
console.log(`generating: ${preset}/example.svg`)
|
console.log(`generating: ${preset}/example.svg`)
|
||||||
const svg = await web.run({ config_presets: `@${preset}`, plugins_errors_fatal: true })
|
const svg = await web.run({config_presets: `@${preset}`, plugins_errors_fatal: true})
|
||||||
await fs.writeFile(paths.join(__presets, path, "example.svg"), svg)
|
await fs.writeFile(paths.join(__presets, path, "example.svg"), svg)
|
||||||
staged.add(paths.join(__presets, path, "example.svg"))
|
staged.add(paths.join(__presets, path, "example.svg"))
|
||||||
|
|
||||||
//Readme
|
//Readme
|
||||||
console.log(`generating: ${preset}/README.svg`)
|
console.log(`generating: ${preset}/README.svg`)
|
||||||
const { name, description } = await yaml.load(await fs.readFile(paths.join(__presets, preset, "preset.yml")))
|
const {name, description} = await yaml.load(await fs.readFile(paths.join(__presets, preset, "preset.yml")))
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
paths.join(__presets, path, "README.md"),
|
paths.join(__presets, path, "README.md"),
|
||||||
`
|
`
|
||||||
|
|||||||
24
.github/scripts/preview.mjs
vendored
24
.github/scripts/preview.mjs
vendored
@@ -19,22 +19,22 @@ const __preview_templates_ = paths.join(__preview, ".templates_")
|
|||||||
const __preview_about = paths.join(__preview, "about/.statics")
|
const __preview_about = paths.join(__preview, "about/.statics")
|
||||||
|
|
||||||
//Extract from web server
|
//Extract from web server
|
||||||
const { conf, Templates } = await setup({ log: false })
|
const {conf, Templates} = await setup({log: false})
|
||||||
const templates = Object.entries(Templates).map(([name]) => ({ name, enabled: true }))
|
const templates = Object.entries(Templates).map(([name]) => ({name, enabled: true}))
|
||||||
const metadata = Object.fromEntries(
|
const metadata = Object.fromEntries(
|
||||||
Object.entries(conf.metadata.plugins)
|
Object.entries(conf.metadata.plugins)
|
||||||
.map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "category", "web", "supports", "scopes"].includes(key)))])
|
.map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "category", "web", "supports", "scopes"].includes(key)))])
|
||||||
.map(([key, value]) => [key, key === "core" ? { ...value, web: Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value])) } : value]),
|
.map(([key, value]) => [key, key === "core" ? {...value, web: Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value]))} : value]),
|
||||||
)
|
)
|
||||||
const enabled = Object.entries(metadata).filter(([_name, { category }]) => category !== "core").map(([name]) => ({ name, category: metadata[name]?.category ?? "community", enabled: true }))
|
const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category: metadata[name]?.category ?? "community", enabled: true}))
|
||||||
|
|
||||||
//Directories
|
//Directories
|
||||||
await fs.mkdir(__preview, { recursive: true })
|
await fs.mkdir(__preview, {recursive: true})
|
||||||
await fs.mkdir(__preview_js, { recursive: true })
|
await fs.mkdir(__preview_js, {recursive: true})
|
||||||
await fs.mkdir(__preview_css, { recursive: true })
|
await fs.mkdir(__preview_css, {recursive: true})
|
||||||
await fs.mkdir(__preview_templates, { recursive: true })
|
await fs.mkdir(__preview_templates, {recursive: true})
|
||||||
await fs.mkdir(__preview_templates_, { recursive: true })
|
await fs.mkdir(__preview_templates_, {recursive: true})
|
||||||
await fs.mkdir(__preview_about, { recursive: true })
|
await fs.mkdir(__preview_about, {recursive: true})
|
||||||
|
|
||||||
//Web
|
//Web
|
||||||
fs.copyFile(paths.join(__web, "index.html"), paths.join(__preview, "index.html"))
|
fs.copyFile(paths.join(__web, "index.html"), paths.join(__preview, "index.html"))
|
||||||
@@ -49,7 +49,7 @@ for (const template in conf.templates) {
|
|||||||
fs.writeFile(paths.join(__preview_templates_, template), JSON.stringify(conf.templates[template]))
|
fs.writeFile(paths.join(__preview_templates_, template), JSON.stringify(conf.templates[template]))
|
||||||
const __partials = paths.join(__templates, template, "partials")
|
const __partials = paths.join(__templates, template, "partials")
|
||||||
const __preview_partials = paths.join(__preview_templates, template, "partials")
|
const __preview_partials = paths.join(__preview_templates, template, "partials")
|
||||||
await fs.mkdir(__preview_partials, { recursive: true })
|
await fs.mkdir(__preview_partials, {recursive: true})
|
||||||
for (const file of await fs.readdir(__partials))
|
for (const file of await fs.readdir(__partials))
|
||||||
fs.copyFile(paths.join(__partials, file), paths.join(__preview_partials, file))
|
fs.copyFile(paths.join(__partials, file), paths.join(__preview_partials, file))
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ fs.copyFile(paths.join(__node_modules, "prismjs/components/prism-markdown.min.js
|
|||||||
fs.copyFile(paths.join(__node_modules, "clipboard/dist/clipboard.min.js"), paths.join(__preview_js, "clipboard.min.js"))
|
fs.copyFile(paths.join(__node_modules, "clipboard/dist/clipboard.min.js"), paths.join(__preview_js, "clipboard.min.js"))
|
||||||
//Meta
|
//Meta
|
||||||
fs.writeFile(paths.join(__preview, ".version"), JSON.stringify(`${conf.package.version}-preview`))
|
fs.writeFile(paths.join(__preview, ".version"), JSON.stringify(`${conf.package.version}-preview`))
|
||||||
fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({ by: "metrics", link: "https://github.com/lowlighter/metrics" }))
|
fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({by: "metrics", link: "https://github.com/lowlighter/metrics"}))
|
||||||
//About
|
//About
|
||||||
fs.copyFile(paths.join(__web, "about", "index.html"), paths.join(__preview, "about", "index.html"))
|
fs.copyFile(paths.join(__web, "about", "index.html"), paths.join(__preview, "about", "index.html"))
|
||||||
for (const file of await fs.readdir(__web_about)) {
|
for (const file of await fs.readdir(__web_about)) {
|
||||||
|
|||||||
8
.github/scripts/release.mjs
vendored
8
.github/scripts/release.mjs
vendored
@@ -26,7 +26,7 @@ if (!version)
|
|||||||
console.log(`Version: ${version}`)
|
console.log(`Version: ${version}`)
|
||||||
|
|
||||||
//Load related pr
|
//Load related pr
|
||||||
const { data: { items: prs } } = await rest.search.issuesAndPullRequests({
|
const {data: {items: prs}} = await rest.search.issuesAndPullRequests({
|
||||||
q: `repo:${repository.owner}/${repository.name} is:pr is:merged author:${maintainer} assignee:${maintainer} Release ${version} in:title`,
|
q: `repo:${repository.owner}/${repository.name} is:pr is:merged author:${maintainer} assignee:${maintainer} Release ${version} in:title`,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -40,9 +40,9 @@ console.log(`Using pr#${patchnote.number}: ${patchnote.title}`)
|
|||||||
|
|
||||||
//Check whether release already exists
|
//Check whether release already exists
|
||||||
try {
|
try {
|
||||||
const { data: { id } } = await rest.repos.getReleaseByTag({ owner: repository.owner, repo: repository.name, tag: version })
|
const {data: {id}} = await rest.repos.getReleaseByTag({owner: repository.owner, repo: repository.name, tag: version})
|
||||||
console.log(`Release ${version} already exists (#${id}), will replace it`)
|
console.log(`Release ${version} already exists (#${id}), will replace it`)
|
||||||
await rest.repos.deleteRelease({ owner: repository.owner, repo: repository.name, release_id: id })
|
await rest.repos.deleteRelease({owner: repository.owner, repo: repository.name, release_id: id})
|
||||||
console.log(`Deleting tag ${version}`)
|
console.log(`Deleting tag ${version}`)
|
||||||
await git.push(["--delete", "origin", version])
|
await git.push(["--delete", "origin", version])
|
||||||
await new Promise(solve => setTimeout(solve, 15 * 1000))
|
await new Promise(solve => setTimeout(solve, 15 * 1000))
|
||||||
@@ -52,5 +52,5 @@ catch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Publish release
|
//Publish release
|
||||||
await rest.repos.createRelease({ owner: repository.owner, repo: repository.name, tag_name: version, name: `Version ${version.replace(/^v/g, "")}`, body: patchnote.body })
|
await rest.repos.createRelease({owner: repository.owner, repo: repository.name, tag_name: version, name: `Version ${version.replace(/^v/g, "")}`, body: patchnote.body})
|
||||||
console.log(`Successfully published`)
|
console.log(`Successfully published`)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import core from "@actions/core"
|
import core from "@actions/core"
|
||||||
import github from "@actions/github"
|
import github from "@actions/github"
|
||||||
import octokit from "@octokit/graphql"
|
import octokit from "@octokit/graphql"
|
||||||
import fs from "fs/promises"
|
|
||||||
import processes from "child_process"
|
import processes from "child_process"
|
||||||
|
import fs from "fs/promises"
|
||||||
import paths from "path"
|
import paths from "path"
|
||||||
import sgit from "simple-git"
|
import sgit from "simple-git"
|
||||||
import mocks from "../../../tests/mocks/index.mjs"
|
import mocks from "../../../tests/mocks/index.mjs"
|
||||||
@@ -22,7 +22,8 @@ const debugged = []
|
|||||||
const preset = {}
|
const preset = {}
|
||||||
|
|
||||||
//Info logger
|
//Info logger
|
||||||
const info = (left, right, {token = false} = {}) => console.log(`${`${left}`.padEnd(63 + 9 * (/0m$/.test(left)))} │ ${
|
const info = (left, right, {token = false} = {}) =>
|
||||||
|
console.log(`${`${left}`.padEnd(63 + 9 * (/0m$/.test(left)))} │ ${
|
||||||
Array.isArray(right)
|
Array.isArray(right)
|
||||||
? right.join(", ") || "(none)"
|
? right.join(", ") || "(none)"
|
||||||
: right === undefined
|
: right === undefined
|
||||||
@@ -37,7 +38,7 @@ info.section = (left = "", right = " ") => info(`\x1b[36m${left}\x1b[0m`, right)
|
|||||||
info.group = ({metadata, name, inputs}) => {
|
info.group = ({metadata, name, inputs}) => {
|
||||||
info.section(metadata.plugins[name]?.name?.match(/(?<section>[\w\s]+)/i)?.groups?.section?.trim(), " ")
|
info.section(metadata.plugins[name]?.name?.match(/(?<section>[\w\s]+)/i)?.groups?.section?.trim(), " ")
|
||||||
for (const [input, value] of Object.entries(inputs))
|
for (const [input, value] of Object.entries(inputs))
|
||||||
info(metadata.plugins[name]?.inputs[input]?.description?.split("\n")[0] ?? metadata.plugins[name]?.inputs[input]?.description ?? input, `${input in preset ? "*" : ""}${value}`, {token:metadata.plugins[name]?.inputs[input]?.type === "token"})
|
info(metadata.plugins[name]?.inputs[input]?.description?.split("\n")[0] ?? metadata.plugins[name]?.inputs[input]?.description ?? input, `${input in preset ? "*" : ""}${value}`, {token: metadata.plugins[name]?.inputs[input]?.type === "token"})
|
||||||
}
|
}
|
||||||
info.break = () => console.log("─".repeat(88))
|
info.break = () => console.log("─".repeat(88))
|
||||||
|
|
||||||
@@ -70,14 +71,12 @@ async function retry(func, {retries = 1, delay = 0} = {}) {
|
|||||||
|
|
||||||
//Process exit
|
//Process exit
|
||||||
function quit(reason) {
|
function quit(reason) {
|
||||||
const code = {success:0, skipped:0, failed:1}[reason] ?? 0
|
const code = {success: 0, skipped: 0, failed: 1}[reason] ?? 0
|
||||||
process.exit(code)
|
process.exit(code)
|
||||||
}
|
} //=====================================================================================================
|
||||||
|
|
||||||
//=====================================================================================================
|
|
||||||
|
|
||||||
//Runner
|
//Runner
|
||||||
(async function() {
|
|
||||||
|
;(async function() {
|
||||||
try {
|
try {
|
||||||
//Initialization
|
//Initialization
|
||||||
info.break()
|
info.break()
|
||||||
@@ -96,9 +95,9 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Load configuration
|
//Load configuration
|
||||||
const {conf, Plugins, Templates} = await setup({log:false, community:{templates:core.getInput("setup_community_templates")}})
|
const {conf, Plugins, Templates} = await setup({log: false, community: {templates: core.getInput("setup_community_templates")}})
|
||||||
const {metadata} = conf
|
const {metadata} = conf
|
||||||
conf.settings.extras = {default:true}
|
conf.settings.extras = {default: true}
|
||||||
info("Setup", "complete")
|
info("Setup", "complete")
|
||||||
info("Version", conf.package.version)
|
info("Version", conf.package.version)
|
||||||
|
|
||||||
@@ -111,42 +110,42 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Core inputs
|
//Core inputs
|
||||||
Object.assign(preset, await presets(core.getInput("config_presets"), {log:false, core}))
|
Object.assign(preset, await presets(core.getInput("config_presets"), {log: false, core}))
|
||||||
const {
|
const {
|
||||||
user:_user,
|
user: _user,
|
||||||
repo:_repo,
|
repo: _repo,
|
||||||
token,
|
token,
|
||||||
template,
|
template,
|
||||||
query,
|
query,
|
||||||
"setup.community.templates":_templates,
|
"setup.community.templates": _templates,
|
||||||
filename:_filename,
|
filename: _filename,
|
||||||
optimize,
|
optimize,
|
||||||
verify,
|
verify,
|
||||||
"markdown.cache":_markdown_cache,
|
"markdown.cache": _markdown_cache,
|
||||||
debug,
|
debug,
|
||||||
"debug.flags":dflags,
|
"debug.flags": dflags,
|
||||||
"debug.print":dprint,
|
"debug.print": dprint,
|
||||||
"use.mocked.data":mocked,
|
"use.mocked.data": mocked,
|
||||||
dryrun,
|
dryrun,
|
||||||
"plugins.errors.fatal":die,
|
"plugins.errors.fatal": die,
|
||||||
"committer.token":_token,
|
"committer.token": _token,
|
||||||
"committer.branch":_branch,
|
"committer.branch": _branch,
|
||||||
"committer.message":_message,
|
"committer.message": _message,
|
||||||
"committer.gist":_gist,
|
"committer.gist": _gist,
|
||||||
"use.prebuilt.image":_image,
|
"use.prebuilt.image": _image,
|
||||||
retries,
|
retries,
|
||||||
"retries.delay":retries_delay,
|
"retries.delay": retries_delay,
|
||||||
"retries.output.action":retries_output_action,
|
"retries.output.action": retries_output_action,
|
||||||
"retries.delay.output.action":retries_delay_output_action,
|
"retries.delay.output.action": retries_delay_output_action,
|
||||||
"output.action":_action,
|
"output.action": _action,
|
||||||
"output.condition":_output_condition,
|
"output.condition": _output_condition,
|
||||||
delay,
|
delay,
|
||||||
"notice.release":_notice_releases,
|
"notice.release": _notice_releases,
|
||||||
...config
|
...config
|
||||||
} = metadata.plugins.core.inputs.action({core, preset})
|
} = metadata.plugins.core.inputs.action({core, preset})
|
||||||
const q = {...query, ...(_repo ? {repo:_repo} : null), template}
|
const q = {...query, ...(_repo ? {repo: _repo} : null), template}
|
||||||
const _output = ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(config["config.output"]) ? config["config.output"] : metadata.templates[template]?.formats?.[0] ?? null
|
const _output = ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(config["config.output"]) ? config["config.output"] : metadata.templates[template]?.formats?.[0] ?? null
|
||||||
const filename = _filename.replace(/[*]/g, {jpeg:"jpg", markdown:"md", "markdown-pdf":"pdf", insights:"html"}[_output] ?? _output ?? "*")
|
const filename = _filename.replace(/[*]/g, {jpeg: "jpg", markdown: "md", "markdown-pdf": "pdf", insights: "html"}[_output] ?? _output ?? "*")
|
||||||
|
|
||||||
//Docker image
|
//Docker image
|
||||||
if (_image)
|
if (_image)
|
||||||
@@ -162,7 +161,7 @@ function quit(reason) {
|
|||||||
q["debug.flags"] = dflags.join(" ")
|
q["debug.flags"] = dflags.join(" ")
|
||||||
|
|
||||||
//Token for data gathering
|
//Token for data gathering
|
||||||
info("GitHub token", token, {token:true})
|
info("GitHub token", token, {token: true})
|
||||||
//A GitHub token should start with "gh" along an additional letter for type
|
//A GitHub token should start with "gh" along an additional letter for type
|
||||||
//See https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats
|
//See https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats
|
||||||
info("GitHub token format", /^gh[pousr]_/.test(token) ? "correct" : "(old or invalid)")
|
info("GitHub token format", /^gh[pousr]_/.test(token) ? "correct" : "(old or invalid)")
|
||||||
@@ -171,7 +170,7 @@ function quit(reason) {
|
|||||||
conf.settings.token = token
|
conf.settings.token = token
|
||||||
const api = {}
|
const api = {}
|
||||||
const resources = {}
|
const resources = {}
|
||||||
api.graphql = octokit.graphql.defaults({headers:{authorization:`token ${token}`}})
|
api.graphql = octokit.graphql.defaults({headers: {authorization: `token ${token}`}})
|
||||||
info("Github GraphQL API", "ok")
|
info("Github GraphQL API", "ok")
|
||||||
const octoraw = github.getOctokit(token)
|
const octoraw = github.getOctokit(token)
|
||||||
api.rest = octoraw.rest
|
api.rest = octoraw.rest
|
||||||
@@ -185,12 +184,12 @@ function quit(reason) {
|
|||||||
//Test token validity and requests count
|
//Test token validity and requests count
|
||||||
else if (!/^NOT_NEEDED$/.test(token)) {
|
else if (!/^NOT_NEEDED$/.test(token)) {
|
||||||
//Check rate limit
|
//Check rate limit
|
||||||
const {data} = await api.rest.rateLimit.get().catch(() => ({data:{resources:{}}}))
|
const {data} = await api.rest.rateLimit.get().catch(() => ({data: {resources: {}}}))
|
||||||
Object.assign(resources, data.resources)
|
Object.assign(resources, data.resources)
|
||||||
info("API requests (REST)", resources.core ? `${resources.core.remaining}/${resources.core.limit}` : "(unknown)")
|
info("API requests (REST)", resources.core ? `${resources.core.remaining}/${resources.core.limit}` : "(unknown)")
|
||||||
info("API requests (GraphQL)", resources.graphql ? `${resources.graphql.remaining}/${resources.graphql.limit}` : "(unknown)")
|
info("API requests (GraphQL)", resources.graphql ? `${resources.graphql.remaining}/${resources.graphql.limit}` : "(unknown)")
|
||||||
info("API requests (search)", resources.search ? `${resources.search.remaining}/${resources.search.limit}` : "(unknown)")
|
info("API requests (search)", resources.search ? `${resources.search.remaining}/${resources.search.limit}` : "(unknown)")
|
||||||
if ((!resources.core.remaining)||(!resources.graphql.remaining)) {
|
if ((!resources.core.remaining) || (!resources.graphql.remaining)) {
|
||||||
console.warn("::warning::It seems you have reached your API requests limit. Please retry later.")
|
console.warn("::warning::It seems you have reached your API requests limit. Please retry later.")
|
||||||
info.break()
|
info.break()
|
||||||
console.log("Nothing can be done currently, thanks for using metrics!")
|
console.log("Nothing can be done currently, thanks for using metrics!")
|
||||||
@@ -217,7 +216,7 @@ function quit(reason) {
|
|||||||
|
|
||||||
//Check for new versions
|
//Check for new versions
|
||||||
if (_notice_releases) {
|
if (_notice_releases) {
|
||||||
const {data:[{tag_name:tag}]} = await rest.repos.listReleases({owner:"lowlighter", repo:"metrics"})
|
const {data: [{tag_name: tag}]} = await rest.repos.listReleases({owner: "lowlighter", repo: "metrics"})
|
||||||
const current = Number(conf.package.version.match(/(\d+\.\d+)/)?.[1] ?? 0)
|
const current = Number(conf.package.version.match(/(\d+\.\d+)/)?.[1] ?? 0)
|
||||||
const latest = Number(tag.match(/(\d+\.\d+)/)?.[1] ?? 0)
|
const latest = Number(tag.match(/(\d+\.\d+)/)?.[1] ?? 0)
|
||||||
if (latest > current)
|
if (latest > current)
|
||||||
@@ -254,7 +253,7 @@ function quit(reason) {
|
|||||||
committer.merge = _action.match(/^pull-request-(?<method>merge|squash|rebase)$/)?.groups?.method ?? null
|
committer.merge = _action.match(/^pull-request-(?<method>merge|squash|rebase)$/)?.groups?.method ?? null
|
||||||
committer.branch = _branch || github.context.ref.replace(/^refs[/]heads[/]/, "")
|
committer.branch = _branch || github.context.ref.replace(/^refs[/]heads[/]/, "")
|
||||||
committer.head = committer.pr ? `metrics-run-${github.context.runId}` : committer.branch
|
committer.head = committer.pr ? `metrics-run-${github.context.runId}` : committer.branch
|
||||||
info("Committer token", committer.token, {token:true})
|
info("Committer token", committer.token, {token: true})
|
||||||
if (!committer.token)
|
if (!committer.token)
|
||||||
throw new Error("You must provide a valid GitHub token to commit your metrics")
|
throw new Error("You must provide a valid GitHub token to commit your metrics")
|
||||||
info("Committer branch", committer.branch)
|
info("Committer branch", committer.branch)
|
||||||
@@ -273,25 +272,25 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
//Create head branch if needed
|
//Create head branch if needed
|
||||||
try {
|
try {
|
||||||
await committer.rest.git.getRef({...github.context.repo, ref:`heads/${committer.head}`})
|
await committer.rest.git.getRef({...github.context.repo, ref: `heads/${committer.head}`})
|
||||||
info("Committer head branch status", "ok")
|
info("Committer head branch status", "ok")
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
if (/not found/i.test(`${error}`)) {
|
if (/not found/i.test(`${error}`)) {
|
||||||
const {data:{object:{sha}}} = await committer.rest.git.getRef({...github.context.repo, ref:`heads/${committer.branch}`})
|
const {data: {object: {sha}}} = await committer.rest.git.getRef({...github.context.repo, ref: `heads/${committer.branch}`})
|
||||||
info("Committer branch current sha", sha)
|
info("Committer branch current sha", sha)
|
||||||
await committer.rest.git.createRef({...github.context.repo, ref:`refs/heads/${committer.head}`, sha})
|
await committer.rest.git.createRef({...github.context.repo, ref: `refs/heads/${committer.head}`, sha})
|
||||||
info("Committer head branch status", "(created)")
|
info("Committer head branch status", "(created)")
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
throw error
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//Retrieve previous render SHA to be able to update file content through API
|
//Retrieve previous render SHA to be able to update file content through API
|
||||||
committer.sha = null
|
committer.sha = null
|
||||||
try {
|
try {
|
||||||
const {repository:{object:{oid}}} = await graphql(
|
const {repository: {object: {oid}}} = await graphql(
|
||||||
`
|
`
|
||||||
query Sha {
|
query Sha {
|
||||||
repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") {
|
repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") {
|
||||||
@@ -299,7 +298,7 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{headers:{authorization:`token ${committer.token}`}},
|
{headers: {authorization: `token ${committer.token}`}},
|
||||||
)
|
)
|
||||||
committer.sha = oid
|
committer.sha = oid
|
||||||
}
|
}
|
||||||
@@ -308,8 +307,9 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
info("Previous render sha", committer.sha ?? "(none)")
|
info("Previous render sha", committer.sha ?? "(none)")
|
||||||
}
|
}
|
||||||
else if (dryrun)
|
else if (dryrun) {
|
||||||
info("Dry-run", true)
|
info("Dry-run", true)
|
||||||
|
}
|
||||||
|
|
||||||
//SVG file
|
//SVG file
|
||||||
conf.settings.optimize = optimize
|
conf.settings.optimize = optimize
|
||||||
@@ -326,7 +326,7 @@ function quit(reason) {
|
|||||||
|
|
||||||
//Core config
|
//Core config
|
||||||
info.break()
|
info.break()
|
||||||
info.group({metadata, name:"core", inputs:config})
|
info.group({metadata, name: "core", inputs: config})
|
||||||
info("Plugin errors", die ? "(exit with error)" : "(displayed in generated image)")
|
info("Plugin errors", die ? "(exit with error)" : "(displayed in generated image)")
|
||||||
const convert = _output || null
|
const convert = _output || null
|
||||||
Object.assign(q, config)
|
Object.assign(q, config)
|
||||||
@@ -337,7 +337,7 @@ function quit(reason) {
|
|||||||
await new Promise(async (solve, reject) => {
|
await new Promise(async (solve, reject) => {
|
||||||
let stdout = ""
|
let stdout = ""
|
||||||
setTimeout(() => reject("Timeout while waiting for Insights webserver"), 5 * 60 * 1000)
|
setTimeout(() => reject("Timeout while waiting for Insights webserver"), 5 * 60 * 1000)
|
||||||
const web = await processes.spawn("node", ["/metrics/source/app/web/index.mjs"], {env:{...process.env}})
|
const web = await processes.spawn("node", ["/metrics/source/app/web/index.mjs"], {env: {...process.env}})
|
||||||
web.stdout.on("data", data => (console.debug(`web > ${data}`), stdout += data, /Server ready !/.test(stdout) ? solve() : null))
|
web.stdout.on("data", data => (console.debug(`web > ${data}`), stdout += data, /Server ready !/.test(stdout) ? solve() : null))
|
||||||
web.stderr.on("data", data => console.debug(`web > ${data}`))
|
web.stderr.on("data", data => console.debug(`web > ${data}`))
|
||||||
})
|
})
|
||||||
@@ -351,9 +351,9 @@ function quit(reason) {
|
|||||||
|
|
||||||
//Base content
|
//Base content
|
||||||
info.break()
|
info.break()
|
||||||
const {base:parts, repositories:_repositories, ...base} = metadata.plugins.base.inputs.action({core, preset})
|
const {base: parts, repositories: _repositories, ...base} = metadata.plugins.base.inputs.action({core, preset})
|
||||||
conf.settings.repositories = _repositories
|
conf.settings.repositories = _repositories
|
||||||
info.group({metadata, name:"base", inputs:{repositories:conf.settings.repositories, ...base}})
|
info.group({metadata, name: "base", inputs: {repositories: conf.settings.repositories, ...base}})
|
||||||
info("Base sections", parts)
|
info("Base sections", parts)
|
||||||
base.base = false
|
base.base = false
|
||||||
for (const part of conf.settings.plugins.base.parts)
|
for (const part of conf.settings.plugins.base.parts)
|
||||||
@@ -364,7 +364,7 @@ function quit(reason) {
|
|||||||
const plugins = {}
|
const plugins = {}
|
||||||
for (const name of Object.keys(Plugins).filter(key => !["base", "core"].includes(key))) {
|
for (const name of Object.keys(Plugins).filter(key => !["base", "core"].includes(key))) {
|
||||||
//Parse inputs
|
//Parse inputs
|
||||||
const {[name]:enabled, ...inputs} = metadata.plugins[name].inputs.action({core, preset})
|
const {[name]: enabled, ...inputs} = metadata.plugins[name].inputs.action({core, preset})
|
||||||
plugins[name] = {enabled}
|
plugins[name] = {enabled}
|
||||||
//Register user inputs
|
//Register user inputs
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
@@ -386,9 +386,9 @@ function quit(reason) {
|
|||||||
info.break()
|
info.break()
|
||||||
info.section("Rendering")
|
info.section("Rendering")
|
||||||
let rendered = await retry(async () => {
|
let rendered = await retry(async () => {
|
||||||
const {rendered} = await metrics({login:user, q}, {graphql, rest, plugins, conf, die, verify, convert}, {Plugins, Templates})
|
const {rendered} = await metrics({login: user, q}, {graphql, rest, plugins, conf, die, verify, convert}, {Plugins, Templates})
|
||||||
return rendered
|
return rendered
|
||||||
}, {retries, delay:retries_delay})
|
}, {retries, delay: retries_delay})
|
||||||
if (!rendered)
|
if (!rendered)
|
||||||
throw new Error("Could not render metrics")
|
throw new Error("Could not render metrics")
|
||||||
info("Status", "complete")
|
info("Status", "complete")
|
||||||
@@ -409,13 +409,13 @@ function quit(reason) {
|
|||||||
let data = ""
|
let data = ""
|
||||||
await retry(async () => {
|
await retry(async () => {
|
||||||
try {
|
try {
|
||||||
data = `${Buffer.from((await committer.rest.repos.getContent({...github.context.repo, ref:`heads/${committer.head}`, path:filename})).data.content, "base64")}`
|
data = `${Buffer.from((await committer.rest.repos.getContent({...github.context.repo, ref: `heads/${committer.head}`, path: filename})).data.content, "base64")}`
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.response.status !== 404)
|
if (error.response.status !== 404)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
const previous = await svg.hash(data)
|
const previous = await svg.hash(data)
|
||||||
info("Previous hash", previous)
|
info("Previous hash", previous)
|
||||||
const current = await svg.hash(rendered)
|
const current = await svg.hash(rendered)
|
||||||
@@ -430,7 +430,7 @@ function quit(reason) {
|
|||||||
if (dryrun)
|
if (dryrun)
|
||||||
info("Actions to perform", "(none)")
|
info("Actions to perform", "(none)")
|
||||||
else {
|
else {
|
||||||
await fs.mkdir(paths.dirname(paths.join("/renders", filename)), {recursive:true})
|
await fs.mkdir(paths.dirname(paths.join("/renders", filename)), {recursive: true})
|
||||||
await fs.writeFile(paths.join("/renders", filename), Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`))
|
await fs.writeFile(paths.join("/renders", filename), Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`))
|
||||||
info(`Save to /metrics_renders/${filename}`, "ok")
|
info(`Save to /metrics_renders/${filename}`, "ok")
|
||||||
info("Output action", _action)
|
info("Output action", _action)
|
||||||
@@ -454,7 +454,7 @@ function quit(reason) {
|
|||||||
console.debug(`Processing ${path}`)
|
console.debug(`Processing ${path}`)
|
||||||
let sha = null
|
let sha = null
|
||||||
try {
|
try {
|
||||||
const {repository:{object:{oid}}} = await graphql(
|
const {repository: {object: {oid}}} = await graphql(
|
||||||
`
|
`
|
||||||
query Sha {
|
query Sha {
|
||||||
repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") {
|
repository(owner: "${github.context.repo.owner}", name: "${github.context.repo.repo}") {
|
||||||
@@ -462,7 +462,7 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{headers:{authorization:`token ${committer.token}`}},
|
{headers: {authorization: `token ${committer.token}`}},
|
||||||
)
|
)
|
||||||
sha = oid
|
sha = oid
|
||||||
}
|
}
|
||||||
@@ -474,14 +474,14 @@ function quit(reason) {
|
|||||||
...github.context.repo,
|
...github.context.repo,
|
||||||
path,
|
path,
|
||||||
content,
|
content,
|
||||||
message:`${committer.message} (cache)`,
|
message: `${committer.message} (cache)`,
|
||||||
...(sha ? {sha} : {}),
|
...(sha ? {sha} : {}),
|
||||||
branch:committer.pr ? committer.head : committer.branch,
|
branch: committer.pr ? committer.head : committer.branch,
|
||||||
})
|
})
|
||||||
rendered = rendered.replace(match, `<img src="https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/blob/${committer.branch}/${path}">`)
|
rendered = rendered.replace(match, `<img src="https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/blob/${committer.branch}/${path}">`)
|
||||||
info(`Saving ${path}`, "ok")
|
info(`Saving ${path}`, "ok")
|
||||||
}
|
}
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,10 +499,10 @@ function quit(reason) {
|
|||||||
//Upload to gist (this is done as user since committer_token may not have gist rights)
|
//Upload to gist (this is done as user since committer_token may not have gist rights)
|
||||||
if (committer.gist) {
|
if (committer.gist) {
|
||||||
await retry(async () => {
|
await retry(async () => {
|
||||||
await rest.gists.update({gist_id:committer.gist, files:{[filename]:{content:rendered}}})
|
await rest.gists.update({gist_id: committer.gist, files: {[filename]: {content: rendered}}})
|
||||||
info(`Upload to gist ${committer.gist}`, "ok")
|
info(`Upload to gist ${committer.gist}`, "ok")
|
||||||
committer.commit = false
|
committer.commit = false
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Commit metrics
|
//Commit metrics
|
||||||
@@ -510,14 +510,14 @@ function quit(reason) {
|
|||||||
await retry(async () => {
|
await retry(async () => {
|
||||||
await committer.rest.repos.createOrUpdateFileContents({
|
await committer.rest.repos.createOrUpdateFileContents({
|
||||||
...github.context.repo,
|
...github.context.repo,
|
||||||
path:filename,
|
path: filename,
|
||||||
message:committer.message,
|
message: committer.message,
|
||||||
content:Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`).toString("base64"),
|
content: Buffer.from(typeof rendered === "object" ? JSON.stringify(rendered) : `${rendered}`).toString("base64"),
|
||||||
branch:committer.pr ? committer.head : committer.branch,
|
branch: committer.pr ? committer.head : committer.branch,
|
||||||
...(committer.sha ? {sha:committer.sha} : {}),
|
...(committer.sha ? {sha: committer.sha} : {}),
|
||||||
})
|
})
|
||||||
info(`Commit to branch ${committer.branch}`, "ok")
|
info(`Commit to branch ${committer.branch}`, "ok")
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Pull request
|
//Pull request
|
||||||
@@ -526,7 +526,7 @@ function quit(reason) {
|
|||||||
let number = null
|
let number = null
|
||||||
await retry(async () => {
|
await retry(async () => {
|
||||||
try {
|
try {
|
||||||
({data:{number}} = await committer.rest.pulls.create({...github.context.repo, head:committer.head, base:committer.branch, title:`Auto-generated metrics for run #${github.context.runId}`, body:" ", maintainer_can_modify:true}))
|
;({data: {number}} = await committer.rest.pulls.create({...github.context.repo, head: committer.head, base: committer.branch, title: `Auto-generated metrics for run #${github.context.runId}`, body: " ", maintainer_can_modify: true}))
|
||||||
info(`Pull request from ${committer.head} to ${committer.branch}`, "(created)")
|
info(`Pull request from ${committer.head} to ${committer.branch}`, "(created)")
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -535,7 +535,7 @@ function quit(reason) {
|
|||||||
if (/A pull request already exists/.test(error)) {
|
if (/A pull request already exists/.test(error)) {
|
||||||
info(`Pull request from ${committer.head} to ${committer.branch}`, "(already existing)")
|
info(`Pull request from ${committer.head} to ${committer.branch}`, "(already existing)")
|
||||||
const q = `repo:${github.context.repo.owner}/${github.context.repo.repo}+type:pr+state:open+Auto-generated metrics for run #${github.context.runId}+in:title`
|
const q = `repo:${github.context.repo.owner}/${github.context.repo.repo}+type:pr+state:open+Auto-generated metrics for run #${github.context.runId}+in:title`
|
||||||
const prs = (await committer.rest.search.issuesAndPullRequests({q})).data.items.filter(({user:{login}}) => login === "github-actions[bot]")
|
const prs = (await committer.rest.search.issuesAndPullRequests({q})).data.items.filter(({user: {login}}) => login === "github-actions[bot]")
|
||||||
if (prs.length < 1)
|
if (prs.length < 1)
|
||||||
throw new Error("0 matching prs. Cannot proceed.")
|
throw new Error("0 matching prs. Cannot proceed.")
|
||||||
if (prs.length > 1)
|
if (prs.length > 1)
|
||||||
@@ -548,12 +548,12 @@ function quit(reason) {
|
|||||||
committer.merge = false
|
committer.merge = false
|
||||||
number = "(none)"
|
number = "(none)"
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
throw error
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info("Pull request number", number)
|
info("Pull request number", number)
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
//Merge pull request
|
//Merge pull request
|
||||||
if (committer.merge) {
|
if (committer.merge) {
|
||||||
info("Merge method", committer.merge)
|
info("Merge method", committer.merge)
|
||||||
@@ -561,7 +561,7 @@ function quit(reason) {
|
|||||||
do {
|
do {
|
||||||
const success = await retry(async () => {
|
const success = await retry(async () => {
|
||||||
//Check pull request mergeability (https://octokit.github.io/rest.js/v18#pulls-get)
|
//Check pull request mergeability (https://octokit.github.io/rest.js/v18#pulls-get)
|
||||||
const {data:{mergeable, mergeable_state:state}} = await committer.rest.pulls.get({...github.context.repo, pull_number:number})
|
const {data: {mergeable, mergeable_state: state}} = await committer.rest.pulls.get({...github.context.repo, pull_number: number})
|
||||||
console.debug(`Pull request #${number} mergeable state is "${state}"`)
|
console.debug(`Pull request #${number} mergeable state is "${state}"`)
|
||||||
if (mergeable === null) {
|
if (mergeable === null) {
|
||||||
await wait(15)
|
await wait(15)
|
||||||
@@ -570,17 +570,17 @@ function quit(reason) {
|
|||||||
if (!mergeable)
|
if (!mergeable)
|
||||||
throw new Error(`Pull request #${number} is not mergeable (state is "${state}")`)
|
throw new Error(`Pull request #${number} is not mergeable (state is "${state}")`)
|
||||||
//Merge pull request
|
//Merge pull request
|
||||||
await committer.rest.pulls.merge({...github.context.repo, pull_number:number, merge_method:committer.merge})
|
await committer.rest.pulls.merge({...github.context.repo, pull_number: number, merge_method: committer.merge})
|
||||||
info(`Merge #${number} to ${committer.branch}`, "ok")
|
info(`Merge #${number} to ${committer.branch}`, "ok")
|
||||||
return true
|
return true
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
if (!success)
|
if (!success)
|
||||||
continue
|
continue
|
||||||
//Delete head branch
|
//Delete head branch
|
||||||
await retry(async () => {
|
await retry(async () => {
|
||||||
try {
|
try {
|
||||||
await wait(15)
|
await wait(15)
|
||||||
await committer.rest.git.deleteRef({...github.context.repo, ref:`heads/${committer.head}`})
|
await committer.rest.git.deleteRef({...github.context.repo, ref: `heads/${committer.head}`})
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
@@ -588,7 +588,7 @@ function quit(reason) {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
info(`Branch ${committer.head}`, "(deleted)")
|
info(`Branch ${committer.head}`, "(deleted)")
|
||||||
}, {retries:retries_output_action, delay:retries_delay_output_action})
|
}, {retries: retries_output_action, delay: retries_delay_output_action})
|
||||||
break
|
break
|
||||||
} while (--attempts)
|
} while (--attempts)
|
||||||
}
|
}
|
||||||
@@ -596,14 +596,14 @@ function quit(reason) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Consumed API requests
|
//Consumed API requests
|
||||||
if ((!mocked)&&(!/^NOT_NEEDED$/.test(token))) {
|
if ((!mocked) && (!/^NOT_NEEDED$/.test(token))) {
|
||||||
info.break()
|
info.break()
|
||||||
info.section("Consumed API requests")
|
info.section("Consumed API requests")
|
||||||
info(" * provided that no other app used your quota during execution", "")
|
info(" * provided that no other app used your quota during execution", "")
|
||||||
const {data:current} = await rest.rateLimit.get().catch(() => ({data:{resources:{}}}))
|
const {data: current} = await rest.rateLimit.get().catch(() => ({data: {resources: {}}}))
|
||||||
for (const type of ["core", "graphql", "search"]) {
|
for (const type of ["core", "graphql", "search"]) {
|
||||||
const used = resources[type].remaining - current.resources[type].remaining
|
const used = resources[type].remaining - current.resources[type].remaining
|
||||||
info({core:"REST API", graphql:"GraphQL API", search:"Search API"}[type], (Number.isFinite(used)&&(used >= 0)) ? used : "(unknown)")
|
info({core: "REST API", graphql: "GraphQL API", search: "Search API"}[type], (Number.isFinite(used) && (used >= 0)) ? used : "(unknown)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
//Debug
|
//Debug
|
||||||
login = login.replace(/[\n\r]/g, "")
|
login = login.replace(/[\n\r]/g, "")
|
||||||
console.debug(`metrics/compute/${login} > start`)
|
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
|
//Load template
|
||||||
const template = q.template || conf.settings.templates.default
|
const template = q.template || conf.settings.templates.default
|
||||||
@@ -24,14 +24,14 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
//Initialization
|
//Initialization
|
||||||
const pending = []
|
const pending = []
|
||||||
const {queries} = conf
|
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 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 data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
|
||||||
const imports = {
|
const imports = {
|
||||||
plugins:Plugins,
|
plugins: Plugins,
|
||||||
templates:Templates,
|
templates: Templates,
|
||||||
metadata:conf.metadata,
|
metadata: conf.metadata,
|
||||||
...utils,
|
...utils,
|
||||||
...utils.formatters({timeZone:q["config.timezone"]}),
|
...utils.formatters({timeZone: q["config.timezone"]}),
|
||||||
...(/markdown/.test(convert)
|
...(/markdown/.test(convert)
|
||||||
? {
|
? {
|
||||||
imgb64(url, options) {
|
imgb64(url, options) {
|
||||||
@@ -60,7 +60,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
//Executing base plugin and compute metrics
|
//Executing base plugin and compute metrics
|
||||||
console.debug(`metrics/compute/${login} > compute`)
|
console.debug(`metrics/compute/${login} > compute`)
|
||||||
await Plugins.base({login, q, data, rest, graphql, plugins, queries, pending, imports}, conf)
|
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)
|
const promised = await Promise.all(pending)
|
||||||
|
|
||||||
//Check plugins errors
|
//Check plugins errors
|
||||||
@@ -70,7 +70,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
if (die)
|
if (die)
|
||||||
throw new Error("An error occured during rendering, dying")
|
throw new Error("An error occured during rendering, dying")
|
||||||
else
|
else
|
||||||
console.debug(util.inspect(errors, {depth:Infinity, maxStringLength:256}))
|
console.debug(util.inspect(errors, {depth: Infinity, maxStringLength: 256}))
|
||||||
}
|
}
|
||||||
|
|
||||||
//JSON output
|
//JSON output
|
||||||
@@ -89,7 +89,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}))
|
}))
|
||||||
return {rendered, mime:"application/json"}
|
return {rendered, mime: "application/json"}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Markdown output
|
//Markdown output
|
||||||
@@ -100,12 +100,12 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
try {
|
try {
|
||||||
let template = `${q.markdown}`.replace(/\n/g, "")
|
let template = `${q.markdown}`.replace(/\n/g, "")
|
||||||
if (!/^https:/.test(template)) {
|
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}`)
|
console.debug(`metrics/compute/${login} > on ${repo} with default branch ${branch}`)
|
||||||
template = `https://raw.githubusercontent.com/${repo}/${branch}/${template}`
|
template = `https://raw.githubusercontent.com/${repo}/${branch}/${template}`
|
||||||
}
|
}
|
||||||
console.debug(`metrics/compute/${login} > fetching ${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) {
|
catch (error) {
|
||||||
console.debug(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(`metrics/compute/${login} > embed called with`)
|
||||||
console.debug(q)
|
console.debug(q)
|
||||||
let {base} = 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
|
//Translate action syntax to web syntax
|
||||||
let parts = []
|
let parts = []
|
||||||
if (base === true)
|
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]])
|
q = Object.fromEntries([...Object.entries(q).map(([key, value]) => [key.replace(/^plugin_/, "").replace(/_/g, "."), value]), ["base", false]])
|
||||||
//Compute rendering
|
//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 >>>>>>>>>>>>>>>>>>>>>>`)
|
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
|
//Rendering template source
|
||||||
let rendered = source.replace(/\{\{ (?<content>[\s\S]*?) \}\}/g, "{%= $<content> %}")
|
let rendered = source.replace(/\{\{ (?<content>[\s\S]*?) \}\}/g, "{%= $<content> %}")
|
||||||
console.debug(rendered)
|
console.debug(rendered)
|
||||||
for (const delimiters of [{openDelimiter:"<", closeDelimiter:">"}, {openDelimiter:"{", closeDelimiter:"}"}])
|
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})
|
rendered = await ejs.render(rendered, {...data, s: imports.s, f: imports.format, embed}, {views, async: true, ...delimiters})
|
||||||
console.debug(`metrics/compute/${login} > success`)
|
console.debug(`metrics/compute/${login} > success`)
|
||||||
//Output
|
//Output
|
||||||
if (convert === "markdown-pdf") {
|
if (convert === "markdown-pdf") {
|
||||||
return imports.svg.pdf(rendered, {
|
return imports.svg.pdf(rendered, {
|
||||||
paddings:q["config.padding"] || conf.settings.padding,
|
paddings: q["config.padding"] || conf.settings.padding,
|
||||||
style:extras.css,
|
style: extras.css,
|
||||||
twemojis:q["config.twemoji"],
|
twemojis: q["config.twemoji"],
|
||||||
gemojis:q["config.gemoji"],
|
gemojis: q["config.gemoji"],
|
||||||
octicons:q["config.octicon"],
|
octicons: q["config.octicon"],
|
||||||
rest,
|
rest,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return {rendered, mime:"text/html"}
|
return {rendered, mime: "text/html"}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Rendering
|
//Rendering
|
||||||
console.debug(`metrics/compute/${login} > render`)
|
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
|
//Additional transformations
|
||||||
if (q["config.twemoji"])
|
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`)
|
console.debug(`metrics/compute/${login} > verified SVG, no parsing errors found`)
|
||||||
}
|
}
|
||||||
//Resizing
|
//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
|
rendered = resized
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
@@ -212,37 +212,37 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
|
|||||||
//Metrics insights
|
//Metrics insights
|
||||||
metrics.insights = async function({login}, {graphql, rest, conf}, {Plugins, Templates}) {
|
metrics.insights = async function({login}, {graphql, rest, conf}, {Plugins, Templates}) {
|
||||||
const q = {
|
const q = {
|
||||||
template:"classic",
|
template: "classic",
|
||||||
achievements:true,
|
achievements: true,
|
||||||
"achievements.threshold":"X",
|
"achievements.threshold": "X",
|
||||||
isocalendar:true,
|
isocalendar: true,
|
||||||
"isocalendar.duration":"full-year",
|
"isocalendar.duration": "full-year",
|
||||||
languages:true,
|
languages: true,
|
||||||
"languages.limit":0,
|
"languages.limit": 0,
|
||||||
activity:true,
|
activity: true,
|
||||||
"activity.limit":100,
|
"activity.limit": 100,
|
||||||
"activity.days":0,
|
"activity.days": 0,
|
||||||
notable:true,
|
notable: true,
|
||||||
followup:true,
|
followup: true,
|
||||||
"followup.sections":"repositories, user",
|
"followup.sections": "repositories, user",
|
||||||
habits:true,
|
habits: true,
|
||||||
"habits.from":100,
|
"habits.from": 100,
|
||||||
"habits.days":7,
|
"habits.days": 7,
|
||||||
"habits.facts":false,
|
"habits.facts": false,
|
||||||
"habits.charts":true,
|
"habits.charts": true,
|
||||||
introduction:true,
|
introduction: true,
|
||||||
}
|
}
|
||||||
const plugins = {
|
const plugins = {
|
||||||
achievements:{enabled:true},
|
achievements: {enabled: true},
|
||||||
isocalendar:{enabled:true},
|
isocalendar: {enabled: true},
|
||||||
languages:{enabled:true, extras:false},
|
languages: {enabled: true, extras: false},
|
||||||
activity:{enabled:true, markdown:"extended"},
|
activity: {enabled: true, markdown: "extended"},
|
||||||
notable:{enabled:true},
|
notable: {enabled: true},
|
||||||
followup:{enabled:true},
|
followup: {enabled: true},
|
||||||
habits:{enabled:true, extras:false},
|
habits: {enabled: true, extras: false},
|
||||||
introduction:{enabled:true},
|
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
|
//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.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.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.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
|
//Rendering
|
||||||
console.debug(`metrics/compute/${login} > insights > rendering data`)
|
console.debug(`metrics/compute/${login} > insights > rendering data`)
|
||||||
@@ -273,9 +273,9 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
${await page.evaluate(() => document.querySelector("main").outerHTML)}
|
${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>
|
</body>
|
||||||
</html>`
|
</html>`
|
||||||
await browser.close()
|
await browser.close()
|
||||||
return {mime:"text/html", rendered}
|
return {mime: "text/html", rendered}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//Imports
|
//Imports
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import yaml from "js-yaml"
|
import yaml from "js-yaml"
|
||||||
import {marked} from "marked"
|
import { marked } from "marked"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import url from "url"
|
import url from "url"
|
||||||
@@ -13,7 +13,7 @@ const categories = ["core", "github", "social", "community"]
|
|||||||
let previous = null
|
let previous = null
|
||||||
|
|
||||||
//Environment
|
//Environment
|
||||||
const env = {ghactions:`${process.env.GITHUB_ACTIONS}` === "true"}
|
const env = {ghactions: `${process.env.GITHUB_ACTIONS}` === "true"}
|
||||||
|
|
||||||
/**Metadata descriptor parser */
|
/**Metadata descriptor parser */
|
||||||
export default async function metadata({log = true, diff = false} = {}) {
|
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())
|
if (!(await fs.promises.lstat(path.join(___plugins, name))).isDirectory())
|
||||||
continue
|
continue
|
||||||
logger(`metrics/metadata > loading plugin metadata [community/${name}]`)
|
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
|
Plugins[name].community = true
|
||||||
}
|
}
|
||||||
continue
|
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")}`)
|
const descriptor = yaml.load(`${await fs.promises.readFile(__descriptor, "utf-8")}`)
|
||||||
|
|
||||||
//Metadata
|
//Metadata
|
||||||
return {plugins:Plugins, templates:Templates, packaged, descriptor, env}
|
return {plugins: Plugins, templates: Templates, packaged, descriptor, env}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Metadata extractor for inputs */
|
/**Metadata extractor for inputs */
|
||||||
@@ -104,14 +104,14 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
|
|
||||||
//Inputs parser
|
//Inputs parser
|
||||||
{
|
{
|
||||||
meta.inputs = function({data:{user = null} = {}, q, account}, defaults = {}) {
|
meta.inputs = function({data: {user = null} = {}, q, account}, defaults = {}) {
|
||||||
//Support check
|
//Support check
|
||||||
if (!account)
|
if (!account)
|
||||||
console.debug(`metrics/inputs > account type not set for plugin ${name}!`)
|
console.debug(`metrics/inputs > account type not set for plugin ${name}!`)
|
||||||
if (account !== "bypass") {
|
if (account !== "bypass") {
|
||||||
const context = q.repo ? "repository" : account
|
const context = q.repo ? "repository" : account
|
||||||
if (!meta.supports?.includes(context))
|
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
|
//Special values replacer
|
||||||
const replacer = value => {
|
const replacer = value => {
|
||||||
@@ -128,7 +128,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
}
|
}
|
||||||
//Inputs checks
|
//Inputs checks
|
||||||
const result = Object.fromEntries(
|
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
|
//Format key
|
||||||
metadata.to.query(key, {name}),
|
metadata.to.query(key, {name}),
|
||||||
//Format value
|
//Format value
|
||||||
@@ -165,7 +165,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
logger(`metrics/inputs > failed to decode uri : ${value}`)
|
logger(`metrics/inputs > failed to decode uri : ${value}`)
|
||||||
value = defaulted
|
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]] ?? ","
|
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)
|
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]) => [
|
Object.entries(inputs).map(([key, value]) => [
|
||||||
key,
|
key,
|
||||||
{
|
{
|
||||||
comment:"",
|
comment: "",
|
||||||
descriptor:yaml.dump({
|
descriptor: yaml.dump({
|
||||||
[key]:Object.fromEntries(
|
[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]
|
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>"
|
value = "<default-value>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
value = process.env[`INPUT_${key.toUpperCase()}`]?.trim() ?? "<default-value>"
|
value = process.env[`INPUT_${key.toUpperCase()}`]?.trim() ?? "<default-value>"
|
||||||
|
}
|
||||||
|
|
||||||
const unspecified = value === "<default-value>"
|
const unspecified = value === "<default-value>"
|
||||||
//From presets
|
//From presets
|
||||||
@@ -279,32 +280,32 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
q[key] = value
|
q[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return meta.inputs({q, account:"bypass"})
|
return meta.inputs({q, account: "bypass"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Web metadata
|
//Web metadata
|
||||||
{
|
{
|
||||||
meta.web = Object.fromEntries(
|
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
|
//Format key
|
||||||
metadata.to.query(key),
|
metadata.to.query(key),
|
||||||
//Value descriptor
|
//Value descriptor
|
||||||
(() => {
|
(() => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "boolean":
|
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":
|
case "number":
|
||||||
return {text, type:"number", min, max, defaulted}
|
return {text, type: "number", min, max, defaulted}
|
||||||
case "array":
|
case "array":
|
||||||
return {text, type:"text", placeholder:example ?? defaulted, defaulted}
|
return {text, type: "text", placeholder: example ?? defaulted, defaulted}
|
||||||
case "string": {
|
case "string": {
|
||||||
if (Array.isArray(values))
|
if (Array.isArray(values))
|
||||||
return {text, type:"select", values, defaulted}
|
return {text, type: "select", values, defaulted}
|
||||||
return {text, type:"text", placeholder:example ?? defaulted, defaulted}
|
return {text, type: "text", placeholder: example ?? defaulted, defaulted}
|
||||||
}
|
}
|
||||||
case "json":
|
case "json":
|
||||||
return {text, type:"text", placeholder:example ?? defaulted, defaulted}
|
return {text, type: "text", placeholder: example ?? defaulted, defaulted}
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -317,7 +318,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
{
|
{
|
||||||
//Extract demos
|
//Extract demos
|
||||||
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "README.md"), "utf-8")}`
|
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
|
//Compatibility
|
||||||
const templates = {}
|
const templates = {}
|
||||||
@@ -337,7 +338,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
const header = [
|
const header = [
|
||||||
"<table>",
|
"<table>",
|
||||||
` <tr><th colspan="2"><h3>${meta.name}</h3></th></tr>`,
|
` <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>` : "",
|
meta.authors?.length ? `<tr><th>Authors</th><td>${[meta.authors].flat().map(author => `<a href="https://github.com/${author}">@${author}</a>`)}</td></tr>` : "",
|
||||||
" <tr>",
|
" <tr>",
|
||||||
' <th rowspan="3">Supported features<br><sub><a href="metadata.yml">→ Full specification</a></sub></th>',
|
' <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>`,
|
}</td>`,
|
||||||
" </tr>",
|
" </tr>",
|
||||||
" <tr>",
|
" <tr>",
|
||||||
` <td>${[
|
` <td>${
|
||||||
...(meta.scopes ?? []).map(scope => `<code>🔑 ${{public_access:"(scopeless)"}[scope] ?? scope}</code>`),
|
[
|
||||||
|
...(meta.scopes ?? []).map(scope => `<code>🔑 ${{public_access: "(scopeless)"}[scope] ?? scope}</code>`),
|
||||||
...Object.entries(inputs).filter(([_, {type}]) => type === "token").map(([token]) => `<code>🗝️ ${token}</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) : []),
|
...(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>`,
|
].filter(v => v).join(" ") || "<i>No tokens are required for this plugin</i>"
|
||||||
|
}</td>`,
|
||||||
" </tr>",
|
" </tr>",
|
||||||
" <tr>",
|
" <tr>",
|
||||||
demos({colspan:2, wrap:name === "base", examples:meta.examples}),
|
demos({colspan: 2, wrap: name === "base", examples: meta.examples}),
|
||||||
" </tr>",
|
" </tr>",
|
||||||
"</table>",
|
"</table>",
|
||||||
].filter(v => v).join("\n")
|
].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>`)
|
cell.push(`<b>allowed values:</b><ul>${o.values.map(value => `<li>${value}</li>`).join("")}</ul>`)
|
||||||
return ` <tr>
|
return ` <tr>
|
||||||
<td nowrap="nowrap"><h4><code>${option}</code></h4></td>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<td nowrap="nowrap">${cell.join("\n")}</td>
|
<td nowrap="nowrap">${cell.join("\n")}</td>
|
||||||
@@ -462,7 +465,7 @@ metadata.template = async function({__templates, name, plugins}) {
|
|||||||
const header = [
|
const header = [
|
||||||
"<table>",
|
"<table>",
|
||||||
` <tr><th colspan="2"><h3>${meta.name ?? "(unnamed template)"}</h3></th></tr>`,
|
` <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>",
|
" <tr>",
|
||||||
' <th rowspan="3">Supported features<br><sub><a href="metadata.yml">→ Full specification</a></sub></th>',
|
' <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>`,
|
` <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>`,
|
}</td>`,
|
||||||
" </tr>",
|
" </tr>",
|
||||||
" <tr>",
|
" <tr>",
|
||||||
demos({colspan:2, examples:meta.examples}),
|
demos({colspan: 2, examples: meta.examples}),
|
||||||
" </tr>",
|
" </tr>",
|
||||||
"</table>",
|
"</table>",
|
||||||
].join("\n")
|
].join("\n")
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
return {
|
return {
|
||||||
name:meta.name ?? "(unnamed template)",
|
name: meta.name ?? "(unnamed template)",
|
||||||
description:meta.description ?? "",
|
description: meta.description ?? "",
|
||||||
index:meta.index ?? null,
|
index: meta.index ?? null,
|
||||||
formats:meta.formats ?? null,
|
formats: meta.formats ?? null,
|
||||||
supports:meta.supports ?? null,
|
supports: meta.supports ?? null,
|
||||||
readme:{
|
readme: {
|
||||||
demo:demos({examples:meta.examples}),
|
demo: demos({examples: meta.examples}),
|
||||||
compatibility:{
|
compatibility: {
|
||||||
...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => value)),
|
...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])),
|
...Object.fromEntries(Object.entries(compatibility).filter(([_, value]) => !value).map(([key, value]) => [key, meta.formats?.includes("markdown") ? "embed" : value])),
|
||||||
base:true,
|
base: true,
|
||||||
},
|
},
|
||||||
header,
|
header,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import metadata from "./metadata.mjs"
|
|||||||
/**Presets parser */
|
/**Presets parser */
|
||||||
export default async function presets(list, {log = true, core = null} = {}) {
|
export default async function presets(list, {log = true, core = null} = {}) {
|
||||||
//Init
|
//Init
|
||||||
const {plugins} = await metadata({log:false})
|
const {plugins} = await metadata({log: false})
|
||||||
const {"config.presets":files} = plugins.core.inputs({q:{"config.presets":list}, account:"bypass"})
|
const {"config.presets": files} = plugins.core.inputs({q: {"config.presets": list}, account: "bypass"})
|
||||||
const logger = log ? console.debug : () => null
|
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 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"
|
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`)
|
logger(`metrics/presets > ${file} cannot be loaded in current environment ${env}, skipping`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const {schema, with:inputs} = yaml.load(text)
|
const {schema, with: inputs} = yaml.load(text)
|
||||||
logger(`metrics/presets > ${file} preset schema is ${schema}`)
|
logger(`metrics/presets > ${file} preset schema is ${schema}`)
|
||||||
|
|
||||||
//Evaluate preset
|
//Evaluate preset
|
||||||
|
|||||||
@@ -27,15 +27,15 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
const logger = log ? console.debug : () => null
|
const logger = log ? console.debug : () => null
|
||||||
logger("metrics/setup > setup")
|
logger("metrics/setup > setup")
|
||||||
const conf = {
|
const conf = {
|
||||||
authenticated:null,
|
authenticated: null,
|
||||||
templates:{},
|
templates: {},
|
||||||
queries:{},
|
queries: {},
|
||||||
settings:{port:3000},
|
settings: {port: 3000},
|
||||||
metadata:{},
|
metadata: {},
|
||||||
paths:{
|
paths: {
|
||||||
statics:__statics,
|
statics: __statics,
|
||||||
templates:__templates,
|
templates: __templates,
|
||||||
node_modules:__modules,
|
node_modules: __modules,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,13 +64,13 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!conf.settings.templates)
|
if (!conf.settings.templates)
|
||||||
conf.settings.templates = {default:"classic", enabled:[]}
|
conf.settings.templates = {default: "classic", enabled: []}
|
||||||
if (!conf.settings.plugins)
|
if (!conf.settings.plugins)
|
||||||
conf.settings.plugins = {}
|
conf.settings.plugins = {}
|
||||||
conf.settings.community = {...conf.settings.community, ...community}
|
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)
|
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
|
//Load package settings
|
||||||
logger("metrics/setup > load package.json")
|
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)) {
|
if ((Array.isArray(conf.settings.community.templates)) && (conf.settings.community.templates.length)) {
|
||||||
//Clean remote repository
|
//Clean remote repository
|
||||||
logger(`metrics/setup > ${conf.settings.community.templates.length} community templates to install`)
|
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
|
//Download community templates
|
||||||
for (const template of conf.settings.community.templates) {
|
for (const template of conf.settings.community.templates) {
|
||||||
try {
|
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")}`
|
const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}`
|
||||||
logger(`metrics/setup > run ${command}`)
|
logger(`metrics/setup > run ${command}`)
|
||||||
//Clone remote repository
|
//Clone remote repository
|
||||||
processes.execSync(command, {stdio:"ignore"})
|
processes.execSync(command, {stdio: "ignore"})
|
||||||
//Extract template
|
//Extract template
|
||||||
logger(`metrics/setup > extract ${name} from ${repo}@${branch}`)
|
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}`))
|
await fs.promises.rename(path.join(__templates, ".community/source/templates", name), path.join(__templates, `@${name}`))
|
||||||
//JavaScript file
|
//JavaScript file
|
||||||
if (trust)
|
if (trust)
|
||||||
@@ -113,18 +113,18 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
logger(`metrics/setup > @${name} extended from ${inherit}`)
|
logger(`metrics/setup > @${name} extended from ${inherit}`)
|
||||||
await fs.promises.copyFile(path.join(__templates, inherit, "template.mjs"), path.join(__templates, `@${name}`, "template.mjs"))
|
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`)
|
logger(`metrics/setup > @${name} could not extends ${inherit} as it does not exist`)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
logger(`metrics/setup > @${name}/template.mjs does not exist`)
|
logger(`metrics/setup > @${name}/template.mjs does not exist`)
|
||||||
|
}
|
||||||
|
|
||||||
//Clean remote repository
|
//Clean remote repository
|
||||||
logger(`metrics/setup > clean ${repo}@${branch}`)
|
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}`)
|
logger(`metrics/setup > loaded community template ${name}`)
|
||||||
}
|
}
|
||||||
catch (error) {
|
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")
|
logger("metrics/setup > no community templates to install")
|
||||||
|
}
|
||||||
|
|
||||||
//Load templates
|
//Load templates
|
||||||
for (const name of await fs.promises.readdir(__templates)) {
|
for (const name of await fs.promises.readdir(__templates)) {
|
||||||
@@ -145,10 +145,10 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
continue
|
continue
|
||||||
logger(`metrics/setup > load template [${name}]`)
|
logger(`metrics/setup > load template [${name}]`)
|
||||||
//Cache templates files
|
//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 [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"))}`)
|
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
|
//Cache templates scripts
|
||||||
Templates[name] = await (async () => {
|
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 [image, style, fonts] = files.map(file => `${fs.readFileSync(file)}`)
|
||||||
const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`)
|
const partials = JSON.parse(`${fs.readFileSync(path.join(directory, "partials/_.json"))}`)
|
||||||
logger(`metrics/setup > reload template [${name}] > success`)
|
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": {
|
case "community": {
|
||||||
const ___plugins = path.join(__plugins, "community")
|
const ___plugins = path.join(__plugins, "community")
|
||||||
for (const name of await fs.promises.readdir(___plugins))
|
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
|
continue
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -191,7 +191,7 @@ export default async function({log = true, sandbox = false, community = {}} = {}
|
|||||||
//Store authenticated user
|
//Store authenticated user
|
||||||
if (conf.settings.token) {
|
if (conf.settings.token) {
|
||||||
try {
|
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}`)
|
logger(`metrics/setup > setup > authenticated as ${conf.authenticated}`)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -251,7 +251,8 @@ const load = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Create queries formatters
|
//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]
|
let queried = queries[query]
|
||||||
for (const [key, value] of Object.entries(vars))
|
for (const [key, value] of Object.entries(vars))
|
||||||
queried = queried.replace(new RegExp(`[$]${key}`, "g"), value)
|
queried = queried.replace(new RegExp(`[$]${key}`, "g"), value)
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
//Imports
|
//Imports
|
||||||
import octicons from "@primer/octicons"
|
import octicons from "@primer/octicons"
|
||||||
import fs from "fs/promises"
|
|
||||||
import prism_lang from "prismjs/components/index.js"
|
|
||||||
import axios from "axios"
|
import axios from "axios"
|
||||||
import processes from "child_process"
|
import processes from "child_process"
|
||||||
import crypto from "crypto"
|
import crypto from "crypto"
|
||||||
import {minify as csso} from "csso"
|
import { minify as csso } from "csso"
|
||||||
import emoji from "emoji-name-map"
|
import emoji from "emoji-name-map"
|
||||||
import fss from "fs"
|
import fss from "fs"
|
||||||
|
import fs from "fs/promises"
|
||||||
import GIFEncoder from "gifencoder"
|
import GIFEncoder from "gifencoder"
|
||||||
import jimp from "jimp"
|
import jimp from "jimp"
|
||||||
import linguist from "linguist-js"
|
import linguist from "linguist-js"
|
||||||
import {marked} from "marked"
|
import { marked } from "marked"
|
||||||
import minimatch from "minimatch"
|
import minimatch from "minimatch"
|
||||||
import nodechartist from "node-chartist"
|
import nodechartist from "node-chartist"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
@@ -20,6 +19,7 @@ import os from "os"
|
|||||||
import paths from "path"
|
import paths from "path"
|
||||||
import PNG from "png-js"
|
import PNG from "png-js"
|
||||||
import prism from "prismjs"
|
import prism from "prismjs"
|
||||||
|
import prism_lang from "prismjs/components/index.js"
|
||||||
import _puppeteer from "puppeteer"
|
import _puppeteer from "puppeteer"
|
||||||
import purgecss from "purgecss"
|
import purgecss from "purgecss"
|
||||||
import readline from "readline"
|
import readline from "readline"
|
||||||
@@ -34,7 +34,7 @@ import xmlformat from "xml-formatter"
|
|||||||
prism_lang()
|
prism_lang()
|
||||||
|
|
||||||
//Exports
|
//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 */
|
/**Returns module __dirname */
|
||||||
export function __module(module) {
|
export function __module(module) {
|
||||||
@@ -45,25 +45,25 @@ export function __module(module) {
|
|||||||
export const puppeteer = {
|
export const puppeteer = {
|
||||||
async launch() {
|
async launch() {
|
||||||
return _puppeteer.launch({
|
return _puppeteer.launch({
|
||||||
headless:this.headless,
|
headless: this.headless,
|
||||||
executablePath:process.env.PUPPETEER_BROWSER_PATH,
|
executablePath: process.env.PUPPETEER_BROWSER_PATH,
|
||||||
args:this.headless ? ["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] : [],
|
args: this.headless ? ["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"] : [],
|
||||||
ignoreDefaultArgs:["--disable-extensions"],
|
ignoreDefaultArgs: ["--disable-extensions"],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
headless:true,
|
headless: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Plural formatter */
|
/**Plural formatter */
|
||||||
export function s(value, end = "") {
|
export function s(value, end = "") {
|
||||||
return value !== 1 ? {y:"ies", "":"s"}[end] : end
|
return value !== 1 ? {y: "ies", "": "s"}[end] : end
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Formatters */
|
/**Formatters */
|
||||||
export function formatters({timeZone} = {}) {
|
export function formatters({timeZone} = {}) {
|
||||||
//Check options
|
//Check options
|
||||||
try {
|
try {
|
||||||
new Date().toLocaleString("fr", {timeZoneName:"short", timeZone})
|
new Date().toLocaleString("fr", {timeZoneName: "short", timeZone})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
timeZone = undefined
|
timeZone = undefined
|
||||||
@@ -72,7 +72,7 @@ export function formatters({timeZone} = {}) {
|
|||||||
/**Formatter */
|
/**Formatter */
|
||||||
const format = function(n, {sign = false, unit = true, fixed} = {}) {
|
const format = function(n, {sign = false, unit = true, fixed} = {}) {
|
||||||
if (unit) {
|
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)
|
if (n / v >= 1)
|
||||||
return `${(sign) && (n > 0) ? "+" : ""}${(n / v).toFixed(fixed ?? 2).substr(0, 4).replace(/[.]0*$/, "")}${u}`
|
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 */
|
/**Bytes formatter */
|
||||||
format.bytes = function(n) {
|
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)
|
if (n / v >= 1)
|
||||||
return `${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B`
|
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) {
|
format.date = function(string, options) {
|
||||||
if (options.date) {
|
if (options.date) {
|
||||||
delete 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) {
|
if (options.time) {
|
||||||
delete 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))
|
return new Intl.DateTimeFormat("en-GB", {timeZone, ...options}).format(new Date(string))
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ export function shuffle(array) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**Escape html */
|
/**Escape html */
|
||||||
export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) {
|
export function htmlescape(string, u = {"&": true, "<": true, ">": true, '"': true, "'": true}) {
|
||||||
return string
|
return string
|
||||||
.replace(/&(?!(?:amp|lt|gt|quot|apos);)/g, u["&"] ? "&" : "&")
|
.replace(/&(?!(?:amp|lt|gt|quot|apos);)/g, u["&"] ? "&" : "&")
|
||||||
.replace(/</g, u["<"] ? "<" : "<")
|
.replace(/</g, u["<"] ? "<" : "<")
|
||||||
@@ -149,7 +149,7 @@ export function htmlescape(string, u = {"&":true, "<":true, ">":true, '"':true,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**Unescape html */
|
/**Unescape html */
|
||||||
export function htmlunescape(string, u = {"&":true, "<":true, ">":true, '"':true, "'":true}) {
|
export function htmlunescape(string, u = {"&": true, "<": true, ">": true, '"': true, "'": true}) {
|
||||||
return string
|
return string
|
||||||
.replace(/</g, u["<"] ? "<" : "<")
|
.replace(/</g, u["<"] ? "<" : "<")
|
||||||
.replace(/>/g, u[">"] ? ">" : ">")
|
.replace(/>/g, u[">"] ? ">" : ">")
|
||||||
@@ -173,7 +173,7 @@ export async function chartist() {
|
|||||||
/**Language analyzer (single file) */
|
/**Language analyzer (single file) */
|
||||||
export async function language({filename, patch}) {
|
export async function language({filename, patch}) {
|
||||||
console.debug(`metrics/language > ${filename}`)
|
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()
|
const result = (results[filename] ?? "unknown").toLocaleLowerCase()
|
||||||
console.debug(`metrics/language > ${filename} > result: ${result}`)
|
console.debug(`metrics/language > ${filename} > result: ${result}`)
|
||||||
return 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) */
|
/**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} = {}) {
|
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()
|
command = `${prefixed ? prefix : ""} ${command}`.trim()
|
||||||
return new Promise((solve, reject) => {
|
return new Promise((solve, reject) => {
|
||||||
console.debug(`metrics/command/run > ${command}`)
|
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) */
|
/**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
|
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)) {
|
if ((prefixed) && (prefix)) {
|
||||||
args.unshift(command)
|
args.unshift(command)
|
||||||
command = prefix
|
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")
|
throw new Error("`stdout` argument was not provided, use run() instead of spawn() if processing output is not needed")
|
||||||
return new Promise((solve, reject) => {
|
return new Promise((solve, reject) => {
|
||||||
console.debug(`metrics/command/spawn > ${command} with ${args.join(" ")}`)
|
console.debug(`metrics/command/spawn > ${command} with ${args.join(" ")}`)
|
||||||
const child = processes.spawn(command, args, {...options, shell:true, timeout})
|
const child = processes.spawn(command, args, {...options, shell: true, timeout})
|
||||||
const reader = readline.createInterface({input:child.stdout})
|
const reader = readline.createInterface({input: child.stdout})
|
||||||
reader.on("line", stdout)
|
reader.on("line", stdout)
|
||||||
const closed = new Promise(close => reader.on("close", close))
|
const closed = new Promise(close => reader.on("close", close))
|
||||||
child.on("close", async code => {
|
child.on("close", async code => {
|
||||||
@@ -245,18 +245,18 @@ export function highlight(code, lang) {
|
|||||||
/**Markdown-html sanitizer-interpreter */
|
/**Markdown-html sanitizer-interpreter */
|
||||||
export async function markdown(text, {mode = "inline", codelines = Infinity} = {}) {
|
export async function markdown(text, {mode = "inline", codelines = Infinity} = {}) {
|
||||||
//Sanitize user input once to prevent injections and parse into markdown
|
//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
|
//Markdown mode
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "inline": {
|
case "inline": {
|
||||||
rendered = htmlsanitize(
|
rendered = htmlsanitize(
|
||||||
htmlsanitize(rendered, {
|
htmlsanitize(rendered, {
|
||||||
allowedTags:["h1", "h2", "h3", "h4", "h5", "h6", "br", "blockquote", "code", "span"],
|
allowedTags: ["h1", "h2", "h3", "h4", "h5", "h6", "br", "blockquote", "code", "span"],
|
||||||
allowedAttributes:{code:["class"], span:["class"]},
|
allowedAttributes: {code: ["class"], span: ["class"]},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
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"},
|
transformTags: {h1: "b", h2: "b", h3: "b", h4: "b", h5: "b", h6: "b", blockquote: "i"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
@@ -349,7 +349,7 @@ export const svg = {
|
|||||||
}
|
}
|
||||||
//Additional transformations
|
//Additional transformations
|
||||||
if (twemojis)
|
if (twemojis)
|
||||||
rendered = await svg.twemojis(rendered, {custom:false})
|
rendered = await svg.twemojis(rendered, {custom: false})
|
||||||
if ((gemojis) && (rest))
|
if ((gemojis) && (rest))
|
||||||
rendered = await svg.gemojis(rendered, {rest})
|
rendered = await svg.gemojis(rendered, {rest})
|
||||||
if (octicons)
|
if (octicons)
|
||||||
@@ -358,13 +358,13 @@ export const svg = {
|
|||||||
//Render through browser and print pdf
|
//Render through browser and print pdf
|
||||||
console.debug("metrics/svg/pdf > loading svg")
|
console.debug("metrics/svg/pdf > loading svg")
|
||||||
const page = await svg.resize.browser.newPage()
|
const page = await svg.resize.browser.newPage()
|
||||||
page.on("console", ({_text:text}) => console.debug(`metrics/svg/pdf > puppeteer > ${text}`))
|
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"]})
|
await page.setContent(`<main class="markdown-body">${rendered}</main>`, {waitUntil: ["load", "domcontentloaded", "networkidle2"]})
|
||||||
console.debug("metrics/svg/pdf > loaded svg successfully")
|
console.debug("metrics/svg/pdf > loaded svg successfully")
|
||||||
const margins = (Array.isArray(paddings) ? paddings : paddings.split(",")).join(" ")
|
const margins = (Array.isArray(paddings) ? paddings : paddings.split(",")).join(" ")
|
||||||
console.debug(`metrics/svg/pdf > margins set to ${margins}`)
|
console.debug(`metrics/svg/pdf > margins set to ${margins}`)
|
||||||
await page.addStyleTag({
|
await page.addStyleTag({
|
||||||
content:`
|
content: `
|
||||||
main { margin: ${margins}; }
|
main { margin: ${margins}; }
|
||||||
main svg { height: 1em; width: 1em; }
|
main svg { height: 1em; width: 1em; }
|
||||||
${await fs.readFile(paths.join(__module(import.meta.url), "../../../node_modules", "@primer/css/dist/markdown.css")).catch(_ => "")}${style}
|
${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
|
//Result
|
||||||
await page.close()
|
await page.close()
|
||||||
console.debug("metrics/svg/pdf > rendering complete")
|
console.debug("metrics/svg/pdf > rendering complete")
|
||||||
return {rendered, mime:"application/pdf"}
|
return {rendered, mime: "application/pdf"}
|
||||||
},
|
},
|
||||||
/**Render and resize svg */
|
/**Render and resize svg */
|
||||||
async resize(rendered, {paddings, convert, scripts = []}) {
|
async resize(rendered, {paddings, convert, scripts = []}) {
|
||||||
@@ -384,7 +384,7 @@ export const svg = {
|
|||||||
console.debug(`metrics/svg/resize > started ${await svg.resize.browser.version()}`)
|
console.debug(`metrics/svg/resize > started ${await svg.resize.browser.version()}`)
|
||||||
}
|
}
|
||||||
//Format padding
|
//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())
|
paddings = Array.isArray(paddings) ? paddings : `${paddings}`.split(",").map(x => x.trim())
|
||||||
for (const [i, dimension] of [[0, "width"], [1, "height"]]) {
|
for (const [i, dimension] of [[0, "width"], [1, "height"]]) {
|
||||||
let operands = (paddings?.[i] ?? paddings[0])
|
let operands = (paddings?.[i] ?? paddings[0])
|
||||||
@@ -400,18 +400,18 @@ export const svg = {
|
|||||||
//Render through browser and resize height
|
//Render through browser and resize height
|
||||||
console.debug("metrics/svg/resize > loading svg")
|
console.debug("metrics/svg/resize > loading svg")
|
||||||
const page = await svg.resize.browser.newPage()
|
const page = await svg.resize.browser.newPage()
|
||||||
page.setViewport({width:980, height:980})
|
page.setViewport({width: 980, height: 980})
|
||||||
page
|
page
|
||||||
.on("console", message => console.debug(`metrics/svg/resize > puppeteer > ${message.text()}`))
|
.on("console", message => console.debug(`metrics/svg/resize > puppeteer > ${message.text()}`))
|
||||||
.on("pageerror", error => console.debug(`metrics/svg/resize > puppeteer > ${error.message}`))
|
.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")
|
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"
|
let mime = "image/svg+xml"
|
||||||
console.debug("metrics/svg/resize > resizing svg")
|
console.debug("metrics/svg/resize > resizing svg")
|
||||||
let height, resized, width
|
let height, resized, width
|
||||||
try {
|
try {
|
||||||
({resized, width, height} = await page.evaluate(
|
;({resized, width, height} = await page.evaluate(
|
||||||
async (padding, scripts) => {
|
async (padding, scripts) => {
|
||||||
//Execute additional JavaScript
|
//Execute additional JavaScript
|
||||||
for (const script of scripts) {
|
for (const script of scripts) {
|
||||||
@@ -431,7 +431,7 @@ export const svg = {
|
|||||||
console.debug(`animations are ${animated ? "enabled" : "disabled"}`)
|
console.debug(`animations are ${animated ? "enabled" : "disabled"}`)
|
||||||
await new Promise(solve => setTimeout(solve, 2400))
|
await new Promise(solve => setTimeout(solve, 2400))
|
||||||
//Get bounds and resize
|
//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}`)
|
console.debug(`bounds width=${width}, height=${height}`)
|
||||||
height = Math.max(1, Math.ceil(height * padding.height + padding.absolute.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))
|
width = Math.max(1, Math.ceil(width * padding.width + padding.absolute.width))
|
||||||
@@ -445,7 +445,7 @@ export const svg = {
|
|||||||
if (animated)
|
if (animated)
|
||||||
document.querySelector("svg").classList.remove("no-animations")
|
document.querySelector("svg").classList.remove("no-animations")
|
||||||
//Result
|
//Result
|
||||||
return {resized:new XMLSerializer().serializeToString(document.querySelector("svg")), height, width}
|
return {resized: new XMLSerializer().serializeToString(document.querySelector("svg")), height, width}
|
||||||
},
|
},
|
||||||
padding,
|
padding,
|
||||||
scripts,
|
scripts,
|
||||||
@@ -458,7 +458,7 @@ export const svg = {
|
|||||||
//Convert if required
|
//Convert if required
|
||||||
if (convert) {
|
if (convert) {
|
||||||
console.debug(`metrics/svg/resize > convert to ${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}`
|
mime = `image/${convert}`
|
||||||
}
|
}
|
||||||
//Result
|
//Result
|
||||||
@@ -478,7 +478,7 @@ export const svg = {
|
|||||||
}
|
}
|
||||||
//Compute hash
|
//Compute hash
|
||||||
const page = await svg.resize.browser.newPage()
|
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 () => {
|
const data = await page.evaluate(async () => {
|
||||||
document.querySelector("footer")?.remove()
|
document.querySelector("footer")?.remove()
|
||||||
return document.querySelector("svg").outerHTML
|
return document.querySelector("svg").outerHTML
|
||||||
@@ -494,7 +494,7 @@ export const svg = {
|
|||||||
//Load emojis
|
//Load emojis
|
||||||
console.debug("metrics/svg/twemojis > rendering twemojis")
|
console.debug("metrics/svg/twemojis > rendering twemojis")
|
||||||
const emojis = new Map()
|
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))
|
if (!emojis.has(emoji))
|
||||||
emojis.set(emoji, (await axios.get(url)).data.replace(/^<svg /, '<svg class="twemoji" '))
|
emojis.set(emoji, (await axios.get(url)).data.replace(/^<svg /, '<svg class="twemoji" '))
|
||||||
}
|
}
|
||||||
@@ -513,7 +513,7 @@ export const svg = {
|
|||||||
const emojis = new Map()
|
const emojis = new Map()
|
||||||
try {
|
try {
|
||||||
for (const [emoji, url] of Object.entries((await rest.emojis.get()).data).map(([key, value]) => [`:${key}:`, value])) {
|
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="" />`)
|
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)) {
|
for (const size of Object.keys(heights)) {
|
||||||
const octicon = `:octicon-${name}-${size}:`
|
const octicon = `:octicon-${name}-${size}:`
|
||||||
if (new RegExp(`:octicon-${name}(?:-[0-9]+)?:`, "g").test(rendered)) {
|
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)
|
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
|
return rendered
|
||||||
},
|
},
|
||||||
/**Optimizers */
|
/**Optimizers */
|
||||||
optimize:{
|
optimize: {
|
||||||
/**CSS optimizer */
|
/**CSS optimizer */
|
||||||
async css(rendered) {
|
async css(rendered) {
|
||||||
//Extract styles
|
//Extract styles
|
||||||
@@ -558,9 +558,9 @@ export const svg = {
|
|||||||
while (regex.test(rendered)) {
|
while (regex.test(rendered)) {
|
||||||
const style = htmlunescape(rendered.match(regex)?.groups?.style ?? "")
|
const style = htmlunescape(rendered.match(regex)?.groups?.style ?? "")
|
||||||
rendered = rendered.replace(regex, cleaned)
|
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
|
//Purge CSS
|
||||||
const purged = await new purgecss.PurgeCSS().purge({content, 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")
|
console.debug("metrics/svg/optimize/xml > skipped as raw option is enabled")
|
||||||
return rendered
|
return rendered
|
||||||
}
|
}
|
||||||
return xmlformat(rendered, {lineSeparator:"\n", collapseContent:true})
|
return xmlformat(rendered, {lineSeparator: "\n", collapseContent: true})
|
||||||
},
|
},
|
||||||
/**SVG optimizer */
|
/**SVG optimizer */
|
||||||
async svg(rendered, {raw = false} = {}, experimental = new Set()) {
|
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")
|
console.debug("metrics/svg/optimize/svg > this feature require experimental feature flag --optimize-svg")
|
||||||
return rendered
|
return rendered
|
||||||
}
|
}
|
||||||
const {error, data:optimized} = await SVGO.optimize(rendered, {
|
const {error, data: optimized} = await SVGO.optimize(rendered, {
|
||||||
multipass:true,
|
multipass: true,
|
||||||
plugins:SVGO.extendDefaultPlugins([
|
plugins: SVGO.extendDefaultPlugins([
|
||||||
//Additional cleanup
|
//Additional cleanup
|
||||||
{name:"cleanupListOfValues"},
|
{name: "cleanupListOfValues"},
|
||||||
{name:"removeRasterImages"},
|
{name: "removeRasterImages"},
|
||||||
{name:"removeScriptElement"},
|
{name: "removeScriptElement"},
|
||||||
//Force CSS style consistency
|
//Force CSS style consistency
|
||||||
{name:"inlineStyles", active:false},
|
{name: "inlineStyles", active: false},
|
||||||
{name:"removeViewBox", active:false},
|
{name: "removeViewBox", active: false},
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
if (error)
|
if (error)
|
||||||
@@ -616,7 +616,7 @@ export async function record({page, width, height, frames, scale = 1, quality =
|
|||||||
//Register images frames
|
//Register images frames
|
||||||
const images = []
|
const images = []
|
||||||
for (let i = 0; i < frames; i++) {
|
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)
|
await wait(delay / 1000)
|
||||||
if (i % 10 === 0)
|
if (i % 10 === 0)
|
||||||
console.debug(`metrics/record > processed ${i}/${frames} frames`)
|
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)
|
encoder.setQuality(quality)
|
||||||
//Register frames
|
//Register frames
|
||||||
for (let i = 0; i < frames; i++) {
|
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))))
|
encoder.addFrame(await new Promise(solve => buffer.decode(pixels => solve(pixels))))
|
||||||
if (frames % 10 === 0)
|
if (frames % 10 === 0)
|
||||||
console.debug(`metrics/puppeteergif > processed ${i}/${frames} frames`)
|
console.debug(`metrics/puppeteergif > processed ${i}/${frames} frames`)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import app from "./instance.mjs"
|
import app from "./instance.mjs"
|
||||||
;(async function() {
|
;(async function() {
|
||||||
await app({sandbox:process.env.SANDBOX})
|
await app({sandbox: process.env.SANDBOX})
|
||||||
})()
|
})()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default async function({sandbox = false} = {}) {
|
|||||||
//Sandbox mode
|
//Sandbox mode
|
||||||
if (sandbox) {
|
if (sandbox) {
|
||||||
console.debug("metrics/app > sandbox mode is specified, enabling advanced features")
|
console.debug("metrics/app > sandbox mode is specified, enabling advanced features")
|
||||||
Object.assign(conf.settings, {sandbox:true, optimize:true, cached:0, "plugins.default":true, extras:{default:true}})
|
Object.assign(conf.settings, {sandbox: true, optimize: true, cached: 0, "plugins.default": true, extras: {default: true}})
|
||||||
}
|
}
|
||||||
const {token, maxusers = 0, restricted = [], debug = false, cached = 30 * 60 * 1000, port = 3000, ratelimiter = null, plugins = null} = conf.settings
|
const {token, maxusers = 0, restricted = [], debug = false, cached = 30 * 60 * 1000, port = 3000, ratelimiter = null, plugins = null} = conf.settings
|
||||||
const mock = sandbox || conf.settings.mocked
|
const mock = sandbox || conf.settings.mocked
|
||||||
@@ -48,10 +48,10 @@ export default async function({sandbox = false} = {}) {
|
|||||||
conf.settings.token = "MOCKED_TOKEN"
|
conf.settings.token = "MOCKED_TOKEN"
|
||||||
}
|
}
|
||||||
if (debug)
|
if (debug)
|
||||||
console.debug(util.inspect(conf.settings, {depth:Infinity, maxStringLength:256}))
|
console.debug(util.inspect(conf.settings, {depth: Infinity, maxStringLength: 256}))
|
||||||
|
|
||||||
//Load octokits
|
//Load octokits
|
||||||
const api = {graphql:octokit.graphql.defaults({headers:{authorization:`token ${token}`}}), rest:new OctokitRest.Octokit({auth:token})}
|
const api = {graphql: octokit.graphql.defaults({headers: {authorization: `token ${token}`}}), rest: new OctokitRest.Octokit({auth: token})}
|
||||||
//Apply mocking if needed
|
//Apply mocking if needed
|
||||||
if (mock)
|
if (mock)
|
||||||
Object.assign(api, await mocks(api))
|
Object.assign(api, await mocks(api))
|
||||||
@@ -68,8 +68,8 @@ export default async function({sandbox = false} = {}) {
|
|||||||
skip(req, _res) {
|
skip(req, _res) {
|
||||||
return !!cache.get(req.params.login)
|
return !!cache.get(req.params.login)
|
||||||
},
|
},
|
||||||
message:"Too many requests: retry later",
|
message: "Too many requests: retry later",
|
||||||
headers:true,
|
headers: true,
|
||||||
...ratelimiter,
|
...ratelimiter,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -84,24 +84,24 @@ export default async function({sandbox = false} = {}) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
//Base routes
|
//Base routes
|
||||||
const limiter = ratelimit({max:debug ? Number.MAX_SAFE_INTEGER : 60, windowMs:60 * 1000, headers:false})
|
const limiter = ratelimit({max: debug ? Number.MAX_SAFE_INTEGER : 60, windowMs: 60 * 1000, headers: false})
|
||||||
const metadata = Object.fromEntries(
|
const metadata = Object.fromEntries(
|
||||||
Object.entries(conf.metadata.plugins)
|
Object.entries(conf.metadata.plugins)
|
||||||
.map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "category", "web", "supports", "scopes"].includes(key)))])
|
.map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "category", "web", "supports", "scopes"].includes(key)))])
|
||||||
.map(([key, value]) => [key, key === "core" ? {...value, web:Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value]))} : value]),
|
.map(([key, value]) => [key, key === "core" ? {...value, web: Object.fromEntries(Object.entries(value.web).filter(([key]) => /^config[.]/.test(key)).map(([key, value]) => [key.replace(/^config[.]/, ""), value]))} : value]),
|
||||||
)
|
)
|
||||||
const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category:metadata[name]?.category ?? "community", enabled:plugins[name]?.enabled ?? false}))
|
const enabled = Object.entries(metadata).filter(([_name, {category}]) => category !== "core").map(([name]) => ({name, category: metadata[name]?.category ?? "community", enabled: plugins[name]?.enabled ?? false}))
|
||||||
const templates = Object.entries(Templates).map(([name]) => ({name, enabled:(conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false}))
|
const templates = Object.entries(Templates).map(([name]) => ({name, enabled: (conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false}))
|
||||||
const actions = {flush:new Map()}
|
const actions = {flush: new Map()}
|
||||||
const requests = {rest:{limit:0, used:0, remaining:0, reset:NaN}, graphql:{limit:0, used:0, remaining:0, reset:NaN}}
|
const requests = {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}}
|
||||||
let _requests_refresh = false
|
let _requests_refresh = false
|
||||||
if (!conf.settings.notoken) {
|
if (!conf.settings.notoken) {
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
try {
|
try {
|
||||||
const {limit} = await graphql("{ limit:rateLimit {limit remaining reset:resetAt used} }")
|
const {limit} = await graphql("{ limit:rateLimit {limit remaining reset:resetAt used} }")
|
||||||
Object.assign(requests, {
|
Object.assign(requests, {
|
||||||
rest:(await rest.rateLimit.get()).data.rate,
|
rest: (await rest.rateLimit.get()).data.rate,
|
||||||
graphql:{...limit, reset:new Date(limit.reset).getTime()},
|
graphql: {...limit, reset: new Date(limit.reset).getTime()},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -245,9 +245,9 @@ export default async function({sandbox = false} = {}) {
|
|||||||
console.debug(`metrics/app/${login} > awaiting pending request`)
|
console.debug(`metrics/app/${login} > awaiting pending request`)
|
||||||
await pending.get(login)
|
await pending.get(login)
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
pending.set(login, new Promise(_solve => solve = _solve))
|
pending.set(login, new Promise(_solve => solve = _solve))
|
||||||
|
}
|
||||||
|
|
||||||
//Read cached data if possible
|
//Read cached data if possible
|
||||||
if ((!debug) && (cached) && (cache.get(login))) {
|
if ((!debug) && (cached) && (cache.get(login))) {
|
||||||
@@ -273,7 +273,7 @@ export default async function({sandbox = false} = {}) {
|
|||||||
try {
|
try {
|
||||||
//Render
|
//Render
|
||||||
const q = req.query
|
const q = req.query
|
||||||
console.debug(`metrics/app/${login} > ${util.inspect(q, {depth:Infinity, maxStringLength:256})}`)
|
console.debug(`metrics/app/${login} > ${util.inspect(q, {depth: Infinity, maxStringLength: 256})}`)
|
||||||
if ((q["config.presets"]) && (conf.settings.extras?.presets ?? conf.settings.extras?.default ?? false)) {
|
if ((q["config.presets"]) && (conf.settings.extras?.presets ?? conf.settings.extras?.default ?? false)) {
|
||||||
console.debug(`metrics/app/${login} > presets have been specified, loading them`)
|
console.debug(`metrics/app/${login} > presets have been specified, loading them`)
|
||||||
Object.assign(q, await presets(q["config.presets"]))
|
Object.assign(q, await presets(q["config.presets"]))
|
||||||
@@ -283,9 +283,9 @@ export default async function({sandbox = false} = {}) {
|
|||||||
rest,
|
rest,
|
||||||
plugins,
|
plugins,
|
||||||
conf,
|
conf,
|
||||||
die:q["plugins.errors.fatal"] ?? false,
|
die: q["plugins.errors.fatal"] ?? false,
|
||||||
verify:q.verify ?? false,
|
verify: q.verify ?? false,
|
||||||
convert:["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(q["config.output"]) ? q["config.output"] : null,
|
convert: ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf", "insights"].includes(q["config.output"]) ? q["config.output"] : null,
|
||||||
}, {Plugins, Templates})
|
}, {Plugins, Templates})
|
||||||
//Cache
|
//Cache
|
||||||
if ((!debug) && (cached)) {
|
if ((!debug) && (cached)) {
|
||||||
@@ -331,13 +331,14 @@ export default async function({sandbox = false} = {}) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
//Listen
|
//Listen
|
||||||
app.listen(port, () => console.log([
|
app.listen(port, () =>
|
||||||
|
console.log([
|
||||||
`Listening on port │ ${port}`,
|
`Listening on port │ ${port}`,
|
||||||
`Debug mode │ ${debug}`,
|
`Debug mode │ ${debug}`,
|
||||||
`Mocked data │ ${conf.settings.mocked ?? false}`,
|
`Mocked data │ ${conf.settings.mocked ?? false}`,
|
||||||
`Restricted to users │ ${restricted.size ? [...restricted].join(", ") : "(unrestricted)"}`,
|
`Restricted to users │ ${restricted.size ? [...restricted].join(", ") : "(unrestricted)"}`,
|
||||||
`Cached time │ ${cached} seconds`,
|
`Cached time │ ${cached} seconds`,
|
||||||
`Rate limiter │ ${ratelimiter ? util.inspect(ratelimiter, {depth:Infinity, maxStringLength:256}) : "(enabled)"}`,
|
`Rate limiter │ ${ratelimiter ? util.inspect(ratelimiter, {depth: Infinity, maxStringLength: 256}) : "(enabled)"}`,
|
||||||
`Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`,
|
`Max simultaneous users │ ${maxusers ? `${maxusers} users` : "(unrestricted)"}`,
|
||||||
`Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`,
|
`Plugins enabled │ ${enabled.map(({name}) => name).join(", ")}`,
|
||||||
`SVG optimization │ ${conf.settings.optimize ?? false}`,
|
`SVG optimization │ ${conf.settings.optimize ?? false}`,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
//Palette
|
//Palette
|
||||||
try {
|
try {
|
||||||
this.palette = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
|
this.palette = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
||||||
}
|
}
|
||||||
catch (error) {}
|
catch (error) {}
|
||||||
//Embed
|
//Embed
|
||||||
@@ -26,17 +26,17 @@
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
//GitHub limit tracker
|
//GitHub limit tracker
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: requests } = await axios.get("/.requests")
|
const {data: requests} = await axios.get("/.requests")
|
||||||
this.requests = requests
|
this.requests = requests
|
||||||
})(),
|
})(),
|
||||||
//Version
|
//Version
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: version } = await axios.get("/.version")
|
const {data: version} = await axios.get("/.version")
|
||||||
this.version = `v${version}`
|
this.version = `v${version}`
|
||||||
})(),
|
})(),
|
||||||
//Hosted
|
//Hosted
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: hosted } = await axios.get("/.hosted")
|
const {data: hosted} = await axios.get("/.hosted")
|
||||||
this.hosted = hosted
|
this.hosted = hosted
|
||||||
})(),
|
})(),
|
||||||
])
|
])
|
||||||
@@ -89,12 +89,12 @@
|
|||||||
this.metrics = (await axios.get(`/about/query/${this.user}`)).data
|
this.metrics = (await axios.get(`/about/query/${this.user}`)).data
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
this.error = { code: error.response.status, message: error.response.data }
|
this.error = {code: error.response.status, message: error.response.data}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
this.pending = false
|
this.pending = false
|
||||||
try {
|
try {
|
||||||
const { data: requests } = await axios.get("/.requests")
|
const {data: requests} = await axios.get("/.requests")
|
||||||
this.requests = requests
|
this.requests = requests
|
||||||
}
|
}
|
||||||
catch {}
|
catch {}
|
||||||
@@ -104,10 +104,10 @@
|
|||||||
//Computed properties
|
//Computed properties
|
||||||
computed: {
|
computed: {
|
||||||
ranked() {
|
ranked() {
|
||||||
return this.metrics?.rendered.plugins.achievements.list?.filter(({ leaderboard }) => leaderboard).sort((a, b) => a.leaderboard.type.localeCompare(b.leaderboard.type)) ?? []
|
return this.metrics?.rendered.plugins.achievements.list?.filter(({leaderboard}) => leaderboard).sort((a, b) => a.leaderboard.type.localeCompare(b.leaderboard.type)) ?? []
|
||||||
},
|
},
|
||||||
achievements() {
|
achievements() {
|
||||||
return this.metrics?.rendered.plugins.achievements.list?.filter(({ leaderboard }) => !leaderboard).filter(({ title }) => !/(?:automator|octonaut|infographile)/i.test(title)) ?? []
|
return this.metrics?.rendered.plugins.achievements.list?.filter(({leaderboard}) => !leaderboard).filter(({title}) => !/(?:automator|octonaut|infographile)/i.test(title)) ?? []
|
||||||
},
|
},
|
||||||
introduction() {
|
introduction() {
|
||||||
return this.metrics?.rendered.plugins.introduction?.text ?? ""
|
return this.metrics?.rendered.plugins.introduction?.text ?? ""
|
||||||
@@ -138,8 +138,8 @@
|
|||||||
account() {
|
account() {
|
||||||
if (!this.metrics)
|
if (!this.metrics)
|
||||||
return null
|
return null
|
||||||
const { login, name } = this.metrics.rendered.user
|
const {login, name} = this.metrics.rendered.user
|
||||||
return { login, name, avatar: this.metrics.rendered.computed.avatar, type: this.metrics?.rendered.account }
|
return {login, name, avatar: this.metrics.rendered.computed.avatar, type: this.metrics?.rendered.account}
|
||||||
},
|
},
|
||||||
url() {
|
url() {
|
||||||
return `${window.location.protocol}//${window.location.host}/about/${this.user}`
|
return `${window.location.protocol}//${window.location.host}/about/${this.user}`
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
embed: false,
|
embed: false,
|
||||||
localstorage: false,
|
localstorage: false,
|
||||||
searchable: false,
|
searchable: false,
|
||||||
requests: { rest: { limit: 0, used: 0, remaining: 0, reset: NaN }, graphql: { limit: 0, used: 0, remaining: 0, reset: NaN } },
|
requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}},
|
||||||
palette: "light",
|
palette: "light",
|
||||||
metrics: null,
|
metrics: null,
|
||||||
pending: false,
|
pending: false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
;(async function() {
|
;(async function() {
|
||||||
//Init
|
//Init
|
||||||
const { data: metadata } = await axios.get("/.plugins.metadata")
|
const {data: metadata} = await axios.get("/.plugins.metadata")
|
||||||
delete metadata.core.web.output
|
delete metadata.core.web.output
|
||||||
delete metadata.core.web.twemojis
|
delete metadata.core.web.twemojis
|
||||||
//App
|
//App
|
||||||
@@ -11,49 +11,49 @@
|
|||||||
//Interpolate config from browser
|
//Interpolate config from browser
|
||||||
try {
|
try {
|
||||||
this.config.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
this.config.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
this.palette = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
|
this.palette = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
||||||
}
|
}
|
||||||
catch (error) {}
|
catch (error) {}
|
||||||
//Init
|
//Init
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
//GitHub limit tracker
|
//GitHub limit tracker
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: requests } = await axios.get("/.requests")
|
const {data: requests} = await axios.get("/.requests")
|
||||||
this.requests = requests
|
this.requests = requests
|
||||||
})(),
|
})(),
|
||||||
//Templates
|
//Templates
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: templates } = await axios.get("/.templates")
|
const {data: templates} = await axios.get("/.templates")
|
||||||
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
|
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
|
||||||
this.templates.list = templates
|
this.templates.list = templates
|
||||||
this.templates.selected = templates[0]?.name || "classic"
|
this.templates.selected = templates[0]?.name || "classic"
|
||||||
})(),
|
})(),
|
||||||
//Plugins
|
//Plugins
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: plugins } = await axios.get("/.plugins")
|
const {data: plugins} = await axios.get("/.plugins")
|
||||||
this.plugins.list = plugins.filter(({ name }) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization"))
|
this.plugins.list = plugins.filter(({name}) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization"))
|
||||||
const categories = [...new Set(this.plugins.list.map(({ category }) => category))]
|
const categories = [...new Set(this.plugins.list.map(({category}) => category))]
|
||||||
this.plugins.categories = Object.fromEntries(categories.map(category => [category, this.plugins.list.filter(value => category === value.category)]))
|
this.plugins.categories = Object.fromEntries(categories.map(category => [category, this.plugins.list.filter(value => category === value.category)]))
|
||||||
})(),
|
})(),
|
||||||
//Base
|
//Base
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: base } = await axios.get("/.plugins.base")
|
const {data: base} = await axios.get("/.plugins.base")
|
||||||
this.plugins.base = base
|
this.plugins.base = base
|
||||||
this.plugins.enabled.base = Object.fromEntries(base.map(key => [key, true]))
|
this.plugins.enabled.base = Object.fromEntries(base.map(key => [key, true]))
|
||||||
})(),
|
})(),
|
||||||
//Version
|
//Version
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: version } = await axios.get("/.version")
|
const {data: version} = await axios.get("/.version")
|
||||||
this.version = `v${version}`
|
this.version = `v${version}`
|
||||||
})(),
|
})(),
|
||||||
//Hosted
|
//Hosted
|
||||||
(async () => {
|
(async () => {
|
||||||
const { data: hosted } = await axios.get("/.hosted")
|
const {data: hosted} = await axios.get("/.hosted")
|
||||||
this.hosted = hosted
|
this.hosted = hosted
|
||||||
})(),
|
})(),
|
||||||
])
|
])
|
||||||
//Generate placeholder
|
//Generate placeholder
|
||||||
this.mock({ timeout: 200 })
|
this.mock({timeout: 200})
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const marker = document.querySelector("#metrics-end")
|
const marker = document.querySelector("#metrics-end")
|
||||||
if (marker) {
|
if (marker) {
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
},
|
},
|
||||||
components: { Prism: PrismComponent },
|
components: {Prism: PrismComponent},
|
||||||
//Watchers
|
//Watchers
|
||||||
watch: {
|
watch: {
|
||||||
tab: {
|
tab: {
|
||||||
@@ -90,10 +90,10 @@
|
|||||||
tab: "overview",
|
tab: "overview",
|
||||||
palette: "light",
|
palette: "light",
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
requests: { rest: { limit: 0, used: 0, remaining: 0, reset: NaN }, graphql: { limit: 0, used: 0, remaining: 0, reset: NaN } },
|
requests: {rest: {limit: 0, used: 0, remaining: 0, reset: NaN}, graphql: {limit: 0, used: 0, remaining: 0, reset: NaN}},
|
||||||
cached: new Map(),
|
cached: new Map(),
|
||||||
config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, { defaulted }]) => [key, defaulted])),
|
config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, {defaulted}]) => [key, defaulted])),
|
||||||
metadata: Object.fromEntries(Object.entries(metadata).map(([key, { web }]) => [key, web])),
|
metadata: Object.fromEntries(Object.entries(metadata).map(([key, {web}]) => [key, web])),
|
||||||
hosted: null,
|
hosted: null,
|
||||||
docs: {
|
docs: {
|
||||||
overview: {
|
overview: {
|
||||||
@@ -121,15 +121,15 @@
|
|||||||
"base.community": "Community stats",
|
"base.community": "Community stats",
|
||||||
"base.repositories": "Repositories metrics",
|
"base.repositories": "Repositories metrics",
|
||||||
"base.metadata": "Metadata",
|
"base.metadata": "Metadata",
|
||||||
...Object.fromEntries(Object.entries(metadata).map(([key, { name }]) => [key, name])),
|
...Object.fromEntries(Object.entries(metadata).map(([key, {name}]) => [key, name])),
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
descriptions: { ...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, { web }]) => web))) },
|
descriptions: {...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)))},
|
||||||
...(Object.fromEntries(
|
...(Object.fromEntries(
|
||||||
Object.entries(
|
Object.entries(
|
||||||
Object.assign({}, ...Object.entries(metadata).flatMap(([key, { web }]) => web)),
|
Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)),
|
||||||
)
|
)
|
||||||
.map(([key, { defaulted }]) => [key, defaulted]),
|
.map(([key, {defaulted}]) => [key, defaulted]),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
computed: {
|
computed: {
|
||||||
//Unusable plugins
|
//Unusable plugins
|
||||||
unusable() {
|
unusable() {
|
||||||
return this.plugins.list.filter(({ name }) => this.plugins.enabled[name]).filter(({ enabled }) => !enabled).map(({ name }) => name)
|
return this.plugins.list.filter(({name}) => this.plugins.enabled[name]).filter(({enabled}) => !enabled).map(({name}) => name)
|
||||||
},
|
},
|
||||||
//User's avatar
|
//User's avatar
|
||||||
avatar() {
|
avatar() {
|
||||||
@@ -239,13 +239,13 @@
|
|||||||
` base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ") || '""'}`,
|
` base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ") || '""'}`,
|
||||||
...[
|
...[
|
||||||
...Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) =>
|
...Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) =>
|
||||||
` ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`
|
` ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`
|
||||||
),
|
),
|
||||||
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).map(([key]) => ` plugin_${key}: yes`),
|
...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).map(([key]) => ` plugin_${key}: yes`),
|
||||||
...Object.entries(this.plugins.options).filter(([key, value]) => value).filter(([key, value]) => this.plugins.enabled[key.split(".")[0]]).map(([key, value]) =>
|
...Object.entries(this.plugins.options).filter(([key, value]) => value).filter(([key, value]) => this.plugins.enabled[key.split(".")[0]]).map(([key, value]) =>
|
||||||
` plugin_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`
|
` plugin_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`
|
||||||
),
|
),
|
||||||
...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => ` config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`),
|
...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => ` config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? {true: "yes", false: "no"}[value] : value}`),
|
||||||
].sort(),
|
].sort(),
|
||||||
].join("\n")
|
].join("\n")
|
||||||
},
|
},
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
methods: {
|
methods: {
|
||||||
//Refresh computed properties
|
//Refresh computed properties
|
||||||
async refresh() {
|
async refresh() {
|
||||||
const keys = { action: ["scopes", "action"], markdown: ["url", "embed"] }[this.tab]
|
const keys = {action: ["scopes", "action"], markdown: ["url", "embed"]}[this.tab]
|
||||||
if (keys) {
|
if (keys) {
|
||||||
for (const key of keys)
|
for (const key of keys)
|
||||||
this._computedWatchers[key]?.run()
|
this._computedWatchers[key]?.run()
|
||||||
@@ -284,7 +284,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
//Load and render placeholder image
|
//Load and render placeholder image
|
||||||
async mock({ timeout = 600 } = {}) {
|
async mock({timeout = 600} = {}) {
|
||||||
this.refresh()
|
this.refresh()
|
||||||
clearTimeout(this.templates.placeholder.timeout)
|
clearTimeout(this.templates.placeholder.timeout)
|
||||||
this.templates.placeholder.timeout = setTimeout(async () => {
|
this.templates.placeholder.timeout = setTimeout(async () => {
|
||||||
@@ -315,12 +315,12 @@
|
|||||||
this.generated.error = null
|
this.generated.error = null
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
this.generated.error = { code: error.response.status, message: error.response.data }
|
this.generated.error = {code: error.response.status, message: error.response.data}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
this.generated.pending = false
|
this.generated.pending = false
|
||||||
try {
|
try {
|
||||||
const { data: requests } = await axios.get("/.requests")
|
const {data: requests} = await axios.get("/.requests")
|
||||||
this.requests = requests
|
this.requests = requests
|
||||||
}
|
}
|
||||||
catch {}
|
catch {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
;(function({ axios, faker, ejs } = { axios: globalThis.axios, faker: globalThis.faker, ejs: globalThis.ejs }) {
|
;(function({axios, faker, ejs} = {axios: globalThis.axios, faker: globalThis.faker, ejs: globalThis.ejs}) {
|
||||||
//Load assets
|
//Load assets
|
||||||
const cached = new Map()
|
const cached = new Map()
|
||||||
async function load(url) {
|
async function load(url) {
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
//Placeholder function
|
//Placeholder function
|
||||||
globalThis.placeholder = async function(set) {
|
globalThis.placeholder = async function(set) {
|
||||||
//Load templates informations
|
//Load templates informations
|
||||||
let { image, style, fonts, partials } = await load(`/.templates/${set.templates.selected}`)
|
let {image, style, fonts, partials} = await load(`/.templates/${set.templates.selected}`)
|
||||||
await Promise.all(partials.map(async partial => await load(`/.templates/${set.templates.selected}/partials/${escape(partial)}.ejs`)))
|
await Promise.all(partials.map(async partial => await load(`/.templates/${set.templates.selected}/partials/${escape(partial)}.ejs`)))
|
||||||
//Trap includes
|
//Trap includes
|
||||||
image = image.replace(/<%-\s*await include[(](`.*?[.]ejs`)[)]\s*%>/g, (m, g) => `<%- await $include(${g}) %>`)
|
image = image.replace(/<%-\s*await include[(](`.*?[.]ejs`)[)]\s*%>/g, (m, g) => `<%- await $include(${g}) %>`)
|
||||||
@@ -45,11 +45,11 @@
|
|||||||
partials: new Set([...(set.config.order || "").split(",").map(x => x.trim()).filter(x => partials.includes(x)), ...partials]),
|
partials: new Set([...(set.config.order || "").split(",").map(x => x.trim()).filter(x => partials.includes(x)), ...partials]),
|
||||||
//Plural helper
|
//Plural helper
|
||||||
s(value, end = "") {
|
s(value, end = "") {
|
||||||
return value !== 1 ? { y: "ies", "": "s" }[end] : end
|
return value !== 1 ? {y: "ies", "": "s"}[end] : end
|
||||||
},
|
},
|
||||||
//Formatter helper
|
//Formatter helper
|
||||||
f(n, { sign = false } = {}) {
|
f(n, {sign = false} = {}) {
|
||||||
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)
|
if (n / v >= 1)
|
||||||
return `${(sign) && (n > 0) ? "+" : ""}${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}`
|
return `${(sign) && (n > 0) ? "+" : ""}${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")}${u}`
|
||||||
}
|
}
|
||||||
@@ -58,10 +58,10 @@
|
|||||||
//Trap for includes
|
//Trap for includes
|
||||||
async $include(path) {
|
async $include(path) {
|
||||||
const partial = await load(`/.templates/${set.templates.selected}/${escape(path)}`)
|
const partial = await load(`/.templates/${set.templates.selected}/${escape(path)}`)
|
||||||
return await ejs.render(partial, data, { async: true, rmWhitespace: true })
|
return await ejs.render(partial, data, {async: true, rmWhitespace: true})
|
||||||
},
|
},
|
||||||
//Meta-data
|
//Meta-data
|
||||||
meta: { version: set.version, author: "lowlighter", generated: new Date().toGMTString().replace(/GMT$/g, "").trim() },
|
meta: {version: set.version, author: "lowlighter", generated: new Date().toGMTString().replace(/GMT$/g, "").trim()},
|
||||||
//Animated
|
//Animated
|
||||||
animated: false,
|
animated: false,
|
||||||
//Display size
|
//Display size
|
||||||
@@ -70,30 +70,30 @@
|
|||||||
//Config
|
//Config
|
||||||
config: set.config,
|
config: set.config,
|
||||||
//Extras
|
//Extras
|
||||||
extras: { css: options["extras.css"] ?? "" },
|
extras: {css: options["extras.css"] ?? ""},
|
||||||
//Base elements
|
//Base elements
|
||||||
base: set.plugins.enabled.base,
|
base: set.plugins.enabled.base,
|
||||||
//Computed elements
|
//Computed elements
|
||||||
computed: {
|
computed: {
|
||||||
commits: faker.datatype.number(10000),
|
commits: faker.datatype.number(10000),
|
||||||
sponsorships: faker.datatype.number(10),
|
sponsorships: faker.datatype.number(10),
|
||||||
licenses: { favorite: [""], used: { MIT: 1 }, about: {} },
|
licenses: {favorite: [""], used: {MIT: 1}, about: {}},
|
||||||
token: { scopes: [] },
|
token: {scopes: []},
|
||||||
repositories: {
|
repositories: {
|
||||||
watchers: faker.datatype.number(1000),
|
watchers: faker.datatype.number(1000),
|
||||||
stargazers: faker.datatype.number(10000),
|
stargazers: faker.datatype.number(10000),
|
||||||
issues_open: faker.datatype.number(1000),
|
issues_open: faker.datatype.number(1000),
|
||||||
issues_closed: faker.datatype.number(1000),
|
issues_closed: faker.datatype.number(1000),
|
||||||
pr_open: faker.datatype.number(1000),
|
pr_open: faker.datatype.number(1000),
|
||||||
pr_closed: { totalCount: faker.datatype.number(100) },
|
pr_closed: {totalCount: faker.datatype.number(100)},
|
||||||
pr_merged: faker.datatype.number(1000),
|
pr_merged: faker.datatype.number(1000),
|
||||||
forks: faker.datatype.number(1000),
|
forks: faker.datatype.number(1000),
|
||||||
releases: faker.datatype.number(1000),
|
releases: faker.datatype.number(1000),
|
||||||
},
|
},
|
||||||
diskUsage: `${faker.datatype.float({ min: 1, max: 999 }).toFixed(1)}MB`,
|
diskUsage: `${faker.datatype.float({min: 1, max: 999}).toFixed(1)}MB`,
|
||||||
registration: `${faker.datatype.number({ min: 2, max: 10 })} years ago`,
|
registration: `${faker.datatype.number({min: 2, max: 10})} years ago`,
|
||||||
cakeday: false,
|
cakeday: false,
|
||||||
calendar: new Array(14).fill(null).map(_ => ({ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) })),
|
calendar: new Array(14).fill(null).map(_ => ({color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])})),
|
||||||
avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
||||||
},
|
},
|
||||||
//User data
|
//User data
|
||||||
@@ -107,12 +107,12 @@
|
|||||||
websiteUrl: options["pagespeed.url"] || "(attached website)",
|
websiteUrl: options["pagespeed.url"] || "(attached website)",
|
||||||
isHireable: false,
|
isHireable: false,
|
||||||
twitterUsername: options["tweets.user"] || "(attached Twitter account)",
|
twitterUsername: options["tweets.user"] || "(attached Twitter account)",
|
||||||
repositories: { totalCount: faker.datatype.number(100), totalDiskUsage: faker.datatype.number(100000), nodes: [] },
|
repositories: {totalCount: faker.datatype.number(100), totalDiskUsage: faker.datatype.number(100000), nodes: []},
|
||||||
packages: { totalCount: faker.datatype.number(10) },
|
packages: {totalCount: faker.datatype.number(10)},
|
||||||
starredRepositories: { totalCount: faker.datatype.number(1000) },
|
starredRepositories: {totalCount: faker.datatype.number(1000)},
|
||||||
watching: { totalCount: faker.datatype.number(100) },
|
watching: {totalCount: faker.datatype.number(100)},
|
||||||
sponsorshipsAsSponsor: { totalCount: faker.datatype.number(10) },
|
sponsorshipsAsSponsor: {totalCount: faker.datatype.number(10)},
|
||||||
sponsorshipsAsMaintainer: { totalCount: faker.datatype.number(10) },
|
sponsorshipsAsMaintainer: {totalCount: faker.datatype.number(10)},
|
||||||
contributionsCollection: {
|
contributionsCollection: {
|
||||||
totalRepositoriesWithContributedCommits: faker.datatype.number(100),
|
totalRepositoriesWithContributedCommits: faker.datatype.number(100),
|
||||||
totalCommitContributions: faker.datatype.number(10000),
|
totalCommitContributions: faker.datatype.number(10000),
|
||||||
@@ -121,12 +121,12 @@
|
|||||||
totalPullRequestContributions: faker.datatype.number(1000),
|
totalPullRequestContributions: faker.datatype.number(1000),
|
||||||
totalPullRequestReviewContributions: faker.datatype.number(1000),
|
totalPullRequestReviewContributions: faker.datatype.number(1000),
|
||||||
},
|
},
|
||||||
calendar: { contributionCalendar: { weeks: [] } },
|
calendar: {contributionCalendar: {weeks: []}},
|
||||||
repositoriesContributedTo: { totalCount: faker.datatype.number(100) },
|
repositoriesContributedTo: {totalCount: faker.datatype.number(100)},
|
||||||
followers: { totalCount: faker.datatype.number(1000) },
|
followers: {totalCount: faker.datatype.number(1000)},
|
||||||
following: { totalCount: faker.datatype.number(1000) },
|
following: {totalCount: faker.datatype.number(1000)},
|
||||||
issueComments: { totalCount: faker.datatype.number(1000) },
|
issueComments: {totalCount: faker.datatype.number(1000)},
|
||||||
organizations: { totalCount: faker.datatype.number(10) },
|
organizations: {totalCount: faker.datatype.number(10)},
|
||||||
},
|
},
|
||||||
//Plugins
|
//Plugins
|
||||||
plugins: {
|
plugins: {
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
id: faker.datatype.number(100000000000000).toString(),
|
id: faker.datatype.number(100000000000000).toString(),
|
||||||
created_at: faker.date.recent(),
|
created_at: faker.date.recent(),
|
||||||
entities: {
|
entities: {
|
||||||
mentions: [{ start: 22, end: 33, username: "lowlighter" }],
|
mentions: [{start: 22, end: 33, username: "lowlighter"}],
|
||||||
},
|
},
|
||||||
text: 'Checkout metrics from <span class="mention">@lowlighter</span> ! <span class="hashtag">#GitHub</span> ',
|
text: 'Checkout metrics from <span class="mention">@lowlighter</span> ! <span class="hashtag">#GitHub</span> ',
|
||||||
mentions: ["lowlighter"],
|
mentions: ["lowlighter"],
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
? ({
|
? ({
|
||||||
traffic: {
|
traffic: {
|
||||||
views: {
|
views: {
|
||||||
count: `${faker.datatype.number({ min: 10, max: 100 })}.${faker.datatype.number(9)}k`,
|
count: `${faker.datatype.number({min: 10, max: 100})}.${faker.datatype.number(9)}k`,
|
||||||
uniques: `${faker.datatype.number(10)}.${faker.datatype.number(9)}k`,
|
uniques: `${faker.datatype.number(10)}.${faker.datatype.number(9)}k`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -264,7 +264,7 @@
|
|||||||
? {
|
? {
|
||||||
user: {
|
user: {
|
||||||
commits: faker.datatype.number(100),
|
commits: faker.datatype.number(100),
|
||||||
percentage: faker.datatype.float({ max: 1 }),
|
percentage: faker.datatype.float({max: 1}),
|
||||||
maintainer: false,
|
maintainer: false,
|
||||||
stars: faker.datatype.number(100),
|
stars: faker.datatype.number(100),
|
||||||
},
|
},
|
||||||
@@ -365,7 +365,7 @@
|
|||||||
unlock: null,
|
unlock: null,
|
||||||
text: faker.lorem.sentence(),
|
text: faker.lorem.sentence(),
|
||||||
get icon() {
|
get icon() {
|
||||||
const colors = { S: ["#FF0000", "#FF8500"], A: ["#B59151", "#FFD576"], B: ["#7D6CFF", "#B2A8FF"], C: ["#2088FF", "#79B8FF"], $: ["#FF48BD", "#FF92D8"], X: ["#7A7A7A", "#B0B0B0"] }
|
const colors = {S: ["#FF0000", "#FF8500"], A: ["#B59151", "#FFD576"], B: ["#7D6CFF", "#B2A8FF"], C: ["#2088FF", "#79B8FF"], $: ["#FF48BD", "#FF92D8"], X: ["#7A7A7A", "#B0B0B0"]}
|
||||||
return `<g xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke-linejoin="round"><g stroke="#secondary"><path d="M8 43a3 3 0 100 6 3 3 0 000-6zm40 0a3.001 3.001 0 10.002 6.002A3.001 3.001 0 0048 43zm-18 3h-4.971m-11.045 0H11M45 46h-4"/></g><path stroke="#primary" d="M13 51h28M36.992 45.276l6.375-8.017c1.488.63 3.272.29 4.414-.977a3.883 3.883 0 00.658-4.193l-1.96 2.174-1.936-.151-.406-1.955 1.96-2.173a3.898 3.898 0 00-4.107 1.092 3.886 3.886 0 00-.512 4.485l-7.317 7.169c-1.32 1.314-.807 2.59-.236 3.105.67.601 1.888.845 3.067-.56z"/><g stroke="#primary"><path d="M12.652 31.063l9.442 12.578a.512.512 0 01-.087.716l-2.396 1.805a.512.512 0 01-.712-.114L9.46 33.47l-.176-3.557 3.37 1.15zM17.099 43.115l2.395-1.806"/></g></g><path d="M25.68 36.927v-2.54a2.227 2.227 0 01.37-1.265c-.526-.04-3.84-.371-3.84-4.302 0-1.013.305-1.839.915-2.477a4.989 4.989 0 01-.146-1.86c.087-.882.946-.823 2.577.178 1.277-.47 2.852-.47 4.725 0 .248-.303 2.434-1.704 2.658-.268.047.296.016.946-.093 1.95.516.524.776 1.358.78 2.501.007 2.261-1.26 3.687-3.8 4.278.24.436.355.857.346 1.264a117.57 117.57 0 000 2.614c2.43-.744 4.228-2.06 5.395-3.95.837-1.356 1.433-2.932 1.433-4.865 0-2.886-1.175-4.984-2.5-6.388C32.714 19.903 30.266 19 28 19a9.094 9.094 0 00-6.588 2.897C20.028 23.393 19 25.507 19 28.185c0 2.026.701 3.945 1.773 5.38 1.228 1.643 2.864 2.764 4.907 3.362zM52.98 25.002l-3.07 3.065-1.49-1.485M6.98 25.002l-3.07 3.065-1.49-1.485" stroke="#primary" stroke-linejoin="round"/><path d="M19.001 11V9a2 2 0 012-2h14a2 2 0 012 2v2m-21 12.028v-10.03a2 2 0 012-1.998h20a2 2 0 012 2v10.028" stroke="#secondary" stroke-linejoin="round"/><path stroke="#secondary" d="M28.001 7V3M15.039 7.797c-5.297 3.406-9.168 8.837-10.517 15.2m46.737-.936c-1.514-5.949-5.25-11.01-10.273-14.248"/></g>`
|
return `<g xmlns="http://www.w3.org/2000/svg" stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke-linejoin="round"><g stroke="#secondary"><path d="M8 43a3 3 0 100 6 3 3 0 000-6zm40 0a3.001 3.001 0 10.002 6.002A3.001 3.001 0 0048 43zm-18 3h-4.971m-11.045 0H11M45 46h-4"/></g><path stroke="#primary" d="M13 51h28M36.992 45.276l6.375-8.017c1.488.63 3.272.29 4.414-.977a3.883 3.883 0 00.658-4.193l-1.96 2.174-1.936-.151-.406-1.955 1.96-2.173a3.898 3.898 0 00-4.107 1.092 3.886 3.886 0 00-.512 4.485l-7.317 7.169c-1.32 1.314-.807 2.59-.236 3.105.67.601 1.888.845 3.067-.56z"/><g stroke="#primary"><path d="M12.652 31.063l9.442 12.578a.512.512 0 01-.087.716l-2.396 1.805a.512.512 0 01-.712-.114L9.46 33.47l-.176-3.557 3.37 1.15zM17.099 43.115l2.395-1.806"/></g></g><path d="M25.68 36.927v-2.54a2.227 2.227 0 01.37-1.265c-.526-.04-3.84-.371-3.84-4.302 0-1.013.305-1.839.915-2.477a4.989 4.989 0 01-.146-1.86c.087-.882.946-.823 2.577.178 1.277-.47 2.852-.47 4.725 0 .248-.303 2.434-1.704 2.658-.268.047.296.016.946-.093 1.95.516.524.776 1.358.78 2.501.007 2.261-1.26 3.687-3.8 4.278.24.436.355.857.346 1.264a117.57 117.57 0 000 2.614c2.43-.744 4.228-2.06 5.395-3.95.837-1.356 1.433-2.932 1.433-4.865 0-2.886-1.175-4.984-2.5-6.388C32.714 19.903 30.266 19 28 19a9.094 9.094 0 00-6.588 2.897C20.028 23.393 19 25.507 19 28.185c0 2.026.701 3.945 1.773 5.38 1.228 1.643 2.864 2.764 4.907 3.362zM52.98 25.002l-3.07 3.065-1.49-1.485M6.98 25.002l-3.07 3.065-1.49-1.485" stroke="#primary" stroke-linejoin="round"/><path d="M19.001 11V9a2 2 0 012-2h14a2 2 0 012 2v2m-21 12.028v-10.03a2 2 0 012-1.998h20a2 2 0 012 2v10.028" stroke="#secondary" stroke-linejoin="round"/><path stroke="#secondary" d="M28.001 7V3M15.039 7.797c-5.297 3.406-9.168 8.837-10.517 15.2m46.737-.936c-1.514-5.949-5.25-11.01-10.273-14.248"/></g>`
|
||||||
.replace(/#primary/g, colors[this.rank][0])
|
.replace(/#primary/g, colors[this.rank][0])
|
||||||
.replace(/#secondary/g, colors[this.rank][1])
|
.replace(/#secondary/g, colors[this.rank][1])
|
||||||
@@ -374,9 +374,9 @@
|
|||||||
progress: faker.datatype.number(100) / 100,
|
progress: faker.datatype.number(100) / 100,
|
||||||
value: faker.datatype.number(1000),
|
value: faker.datatype.number(1000),
|
||||||
}))
|
}))
|
||||||
.filter(({ rank }) => options["achievements.secrets"] ? true : rank !== "$")
|
.filter(({rank}) => options["achievements.secrets"] ? true : rank !== "$")
|
||||||
.filter(({ rank }) => ({ S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[rank] >= { S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[options["achievements.threshold"]]))
|
.filter(({rank}) => ({S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[rank] >= {S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[options["achievements.threshold"]]))
|
||||||
.sort((a, b) => ({ S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[b.rank] + b.progress * 0.99) - ({ S: 5, A: 4, B: 3, C: 2, $: 1, X: 0 }[a.rank] + a.progress * 0.99))
|
.sort((a, b) => ({S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[b.rank] + b.progress * 0.99) - ({S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}[a.rank] + a.progress * 0.99))
|
||||||
.slice(0, options["achievements.limit"] || Infinity),
|
.slice(0, options["achievements.limit"] || Infinity),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -437,26 +437,26 @@
|
|||||||
sections: options["languages.sections"].split(", ").map(x => x.trim()).filter(x => /^(most-used|recently-used)$/.test(x)),
|
sections: options["languages.sections"].split(", ").map(x => x.trim()).filter(x => /^(most-used|recently-used)$/.test(x)),
|
||||||
details: options["languages.details"].split(",").map(x => x.trim()).filter(x => x),
|
details: options["languages.details"].split(",").map(x => x.trim()).filter(x => x),
|
||||||
get colors() {
|
get colors() {
|
||||||
return Object.fromEntries(Object.entries(this.favorites).map(([key, { color }]) => [key, color]))
|
return Object.fromEntries(Object.entries(this.favorites).map(([key, {color}]) => [key, color]))
|
||||||
},
|
},
|
||||||
total: faker.datatype.number(10000),
|
total: faker.datatype.number(10000),
|
||||||
get stats() {
|
get stats() {
|
||||||
return Object.fromEntries(Object.entries(this.favorites).map(([key, { value }]) => [key, value]))
|
return Object.fromEntries(Object.entries(this.favorites).map(([key, {value}]) => [key, value]))
|
||||||
},
|
},
|
||||||
["stats.recent"]: {
|
["stats.recent"]: {
|
||||||
total: faker.datatype.number(10000),
|
total: faker.datatype.number(10000),
|
||||||
get lines() {
|
get lines() {
|
||||||
return Object.fromEntries(Object.entries(this.favorites).map(([key, { value }]) => [key, value]))
|
return Object.fromEntries(Object.entries(this.favorites).map(([key, {value}]) => [key, value]))
|
||||||
},
|
},
|
||||||
get stats() {
|
get stats() {
|
||||||
return Object.fromEntries(Object.entries(this.favorites).map(([key, { value }]) => [key, value]))
|
return Object.fromEntries(Object.entries(this.favorites).map(([key, {value}]) => [key, value]))
|
||||||
},
|
},
|
||||||
commits: faker.datatype.number(500),
|
commits: faker.datatype.number(500),
|
||||||
files: faker.datatype.number(1000),
|
files: faker.datatype.number(1000),
|
||||||
days: Number(options["languages.recent.days"]),
|
days: Number(options["languages.recent.days"]),
|
||||||
},
|
},
|
||||||
favorites: distribution(7).map((value, index, array) => ({ name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0) })),
|
favorites: distribution(7).map((value, index, array) => ({name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0)})),
|
||||||
recent: distribution(7).map((value, index, array) => ({ name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0) })),
|
recent: distribution(7).map((value, index, array) => ({name: faker.lorem.word(), color: faker.internet.color(), value, size: faker.datatype.number(1000000), x: array.slice(0, index).reduce((a, b) => a + b, 0)})),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null),
|
: null),
|
||||||
@@ -536,7 +536,7 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
indents: { style: "spaces", spaces: 1, tabs: 0 },
|
indents: {style: "spaces", spaces: 1, tabs: 0},
|
||||||
linguist: {
|
linguist: {
|
||||||
available: true,
|
available: true,
|
||||||
get ordered() {
|
get ordered() {
|
||||||
@@ -554,7 +554,7 @@
|
|||||||
? ({
|
? ({
|
||||||
get people() {
|
get people() {
|
||||||
const types = options["people.types"].split(",").map(x => x.trim())
|
const types = options["people.types"].split(",").map(x => x.trim())
|
||||||
.map(x => ({ followed: "following", sponsors: "sponsorshipsAsMaintainer", sponsored: "sponsorshipsAsSponsor", sponsoring: "sponsorshipsAsSponsor" })[x] ?? x)
|
.map(x => ({followed: "following", sponsors: "sponsorshipsAsMaintainer", sponsored: "sponsorshipsAsSponsor", sponsoring: "sponsorshipsAsSponsor"})[x] ?? x)
|
||||||
.filter(x => ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor"].includes(x))
|
.filter(x => ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor"].includes(x))
|
||||||
return {
|
return {
|
||||||
types,
|
types,
|
||||||
@@ -598,8 +598,8 @@
|
|||||||
data: new Array(12).fill(null).map(_ => ({
|
data: new Array(12).fill(null).map(_ => ({
|
||||||
timeUTCHumanReadable: `${new Date().getUTCHours()}:${new Date().getUTCMinutes()}`,
|
timeUTCHumanReadable: `${new Date().getUTCHours()}:${new Date().getUTCMinutes()}`,
|
||||||
color: faker.random.arrayElement(["#9be9a8", "#40c463", "#30a14e", "#216e39"]),
|
color: faker.random.arrayElement(["#9be9a8", "#40c463", "#30a14e", "#216e39"]),
|
||||||
sgv: faker.datatype.number({ min: 40, max: 400 }),
|
sgv: faker.datatype.number({min: 40, max: 400}),
|
||||||
delta: faker.datatype.number({ min: -10, max: 10 }),
|
delta: faker.datatype.number({min: -10, max: 10}),
|
||||||
direction: faker.random.arrayElement(["SingleUp", "DoubleUp", "FortyFiveUp", "Flat", "FortyFiveDown", "SingleDown", "DoubleDown"]),
|
direction: faker.random.arrayElement(["SingleUp", "DoubleUp", "FortyFiveUp", "Flat", "FortyFiveDown", "SingleDown", "DoubleDown"]),
|
||||||
alert: faker.random.arrayElement(["Normal", "Urgent High", "Urgent Low", "High", "Low"]),
|
alert: faker.random.arrayElement(["Normal", "Urgent High", "Urgent Low", "High", "Low"]),
|
||||||
arrowHumanReadable: faker.random.arrayElement(["↑↑", "↑", "↗", "→", "↘", "↓", "↓↓"]),
|
arrowHumanReadable: faker.random.arrayElement(["↑↑", "↑", "↗", "→", "↘", "↓", "↓↓"]),
|
||||||
@@ -611,9 +611,9 @@
|
|||||||
...(set.plugins.enabled.fortune
|
...(set.plugins.enabled.fortune
|
||||||
? ({
|
? ({
|
||||||
fortune: faker.random.arrayElement([
|
fortune: faker.random.arrayElement([
|
||||||
{ chance: .06, color: "#43FD3B", text: "Good news will come to you by mail" },
|
{chance: .06, color: "#43FD3B", text: "Good news will come to you by mail"},
|
||||||
{ chance: .06, color: "#00CBB0", text: "キタ━━━━━━(゚∀゚)━━━━━━ !!!!" },
|
{chance: .06, color: "#00CBB0", text: "キタ━━━━━━(゚∀゚)━━━━━━ !!!!"},
|
||||||
{ chance: 0.03, color: "#FD4D32", text: "Excellent Luck" },
|
{chance: 0.03, color: "#FD4D32", text: "Excellent Luck"},
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
: null),
|
: null),
|
||||||
@@ -624,10 +624,10 @@
|
|||||||
url: options["pagespeed.url"] || "(attached website url)",
|
url: options["pagespeed.url"] || "(attached website url)",
|
||||||
detailed: options["pagespeed.detailed"] || false,
|
detailed: options["pagespeed.detailed"] || false,
|
||||||
scores: [
|
scores: [
|
||||||
{ score: faker.datatype.float({ max: 1 }), title: "Performance" },
|
{score: faker.datatype.float({max: 1}), title: "Performance"},
|
||||||
{ score: faker.datatype.float({ max: 1 }), title: "Accessibility" },
|
{score: faker.datatype.float({max: 1}), title: "Accessibility"},
|
||||||
{ score: faker.datatype.float({ max: 1 }), title: "Best Practices" },
|
{score: faker.datatype.float({max: 1}), title: "Best Practices"},
|
||||||
{ score: faker.datatype.float({ max: 1 }), title: "SEO" },
|
{score: faker.datatype.float({max: 1}), title: "SEO"},
|
||||||
],
|
],
|
||||||
metrics: {
|
metrics: {
|
||||||
observedFirstContentfulPaint: faker.datatype.number(500),
|
observedFirstContentfulPaint: faker.datatype.number(500),
|
||||||
@@ -639,12 +639,12 @@
|
|||||||
maxPotentialFID: faker.datatype.number(500),
|
maxPotentialFID: faker.datatype.number(500),
|
||||||
observedLoad: faker.datatype.number(500),
|
observedLoad: faker.datatype.number(500),
|
||||||
firstMeaningfulPaint: faker.datatype.number(500),
|
firstMeaningfulPaint: faker.datatype.number(500),
|
||||||
observedCumulativeLayoutShift: faker.datatype.float({ max: 1 }),
|
observedCumulativeLayoutShift: faker.datatype.float({max: 1}),
|
||||||
observedSpeedIndex: faker.datatype.number(1000),
|
observedSpeedIndex: faker.datatype.number(1000),
|
||||||
observedSpeedIndexTs: faker.time.recent(),
|
observedSpeedIndexTs: faker.time.recent(),
|
||||||
observedTimeOriginTs: faker.time.recent(),
|
observedTimeOriginTs: faker.time.recent(),
|
||||||
observedLargestContentfulPaint: faker.datatype.number(1000),
|
observedLargestContentfulPaint: faker.datatype.number(1000),
|
||||||
cumulativeLayoutShift: faker.datatype.float({ max: 1 }),
|
cumulativeLayoutShift: faker.datatype.float({max: 1}),
|
||||||
observedFirstPaintTs: faker.time.recent(),
|
observedFirstPaintTs: faker.time.recent(),
|
||||||
observedTraceEndTs: faker.time.recent(),
|
observedTraceEndTs: faker.time.recent(),
|
||||||
largestContentfulPaint: faker.datatype.number(2000),
|
largestContentfulPaint: faker.datatype.number(2000),
|
||||||
@@ -698,14 +698,14 @@
|
|||||||
? ({
|
? ({
|
||||||
discussions: {
|
discussions: {
|
||||||
categories: {
|
categories: {
|
||||||
stats: { "🙏 Q&A": faker.datatype.number(100), "📣 Announcements": faker.datatype.number(100), "💡 Ideas": faker.datatype.number(100), "💬 General": faker.datatype.number(100) },
|
stats: {"🙏 Q&A": faker.datatype.number(100), "📣 Announcements": faker.datatype.number(100), "💡 Ideas": faker.datatype.number(100), "💬 General": faker.datatype.number(100)},
|
||||||
favorite: "📣 Announcements",
|
favorite: "📣 Announcements",
|
||||||
},
|
},
|
||||||
upvotes: { discussions: faker.datatype.number(1000), comments: faker.datatype.number(1000) },
|
upvotes: {discussions: faker.datatype.number(1000), comments: faker.datatype.number(1000)},
|
||||||
started: faker.datatype.number(1000),
|
started: faker.datatype.number(1000),
|
||||||
comments: faker.datatype.number(1000),
|
comments: faker.datatype.number(1000),
|
||||||
answers: faker.datatype.number(1000),
|
answers: faker.datatype.number(1000),
|
||||||
display: { categories: options["discussions.categories"] ? { limit: options["discussions.categories.limit"] || Infinity } : null },
|
display: {categories: options["discussions.categories"] ? {limit: options["discussions.categories.limit"] || Infinity} : null},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null),
|
: null),
|
||||||
@@ -730,7 +730,7 @@
|
|||||||
? ({
|
? ({
|
||||||
topics: {
|
topics: {
|
||||||
mode: options["topics.mode"],
|
mode: options["topics.mode"],
|
||||||
type: { starred: "labels", labels: "labels", mastered: "icons", icons: "icons" }[options["topics.mode"]] || "labels",
|
type: {starred: "labels", labels: "labels", mastered: "icons", icons: "icons"}[options["topics.mode"]] || "labels",
|
||||||
list: new Array(Number(options["topics.limit"]) || 20).fill(null).map(_ => ({
|
list: new Array(Number(options["topics.limit"]) || 20).fill(null).map(_ => ({
|
||||||
name: faker.lorem.words(2),
|
name: faker.lorem.words(2),
|
||||||
description: faker.lorem.sentence(),
|
description: faker.lorem.sentence(),
|
||||||
@@ -759,8 +759,8 @@
|
|||||||
totalCount: faker.datatype.number(100),
|
totalCount: faker.datatype.number(100),
|
||||||
},
|
},
|
||||||
stargazerCount: faker.datatype.number(10000),
|
stargazerCount: faker.datatype.number(10000),
|
||||||
licenseInfo: { nickname: null, name: "MIT License" },
|
licenseInfo: {nickname: null, name: "MIT License"},
|
||||||
primaryLanguage: { color: "#f1e05a", name: "JavaScript" },
|
primaryLanguage: {color: "#f1e05a", name: "JavaScript"},
|
||||||
},
|
},
|
||||||
starred: "1 day ago",
|
starred: "1 day ago",
|
||||||
},
|
},
|
||||||
@@ -779,8 +779,8 @@
|
|||||||
totalCount: faker.datatype.number(100),
|
totalCount: faker.datatype.number(100),
|
||||||
},
|
},
|
||||||
stargazerCount: faker.datatype.number(10000),
|
stargazerCount: faker.datatype.number(10000),
|
||||||
licenseInfo: { nickname: null, name: "License" },
|
licenseInfo: {nickname: null, name: "License"},
|
||||||
primaryLanguage: { color: faker.internet.color(), name: faker.lorem.word() },
|
primaryLanguage: {color: faker.internet.color(), name: faker.lorem.word()},
|
||||||
},
|
},
|
||||||
starred: `${i + 2} days ago`,
|
starred: `${i + 2} days ago`,
|
||||||
})),
|
})),
|
||||||
@@ -825,8 +825,8 @@
|
|||||||
totalCount: faker.datatype.number(100),
|
totalCount: faker.datatype.number(100),
|
||||||
},
|
},
|
||||||
stargazerCount: faker.datatype.number(10000),
|
stargazerCount: faker.datatype.number(10000),
|
||||||
licenseInfo: { nickname: null, name: "License" },
|
licenseInfo: {nickname: null, name: "License"},
|
||||||
primaryLanguage: { color: faker.internet.color(), name: faker.lorem.word() },
|
primaryLanguage: {color: faker.internet.color(), name: faker.lorem.word()},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -861,7 +861,7 @@
|
|||||||
for (let d = -14; d <= 0; d++) {
|
for (let d = -14; d <= 0; d++) {
|
||||||
const date = new Date(Date.now() - d * 24 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
const date = new Date(Date.now() - d * 24 * 60 * 60 * 1000).toISOString().substring(0, 10)
|
||||||
dates.push(date)
|
dates.push(date)
|
||||||
result.total.dates[date] = (total += (result.increments.dates[date] = faker.datatype.number(100)))
|
result.total.dates[date] = total += result.increments.dates[date] = faker.datatype.number(100)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
@@ -884,13 +884,13 @@
|
|||||||
percents -= result.percent
|
percents -= result.percent
|
||||||
result.percent /= 100
|
result.percent /= 100
|
||||||
}
|
}
|
||||||
results.filter(({ name }) => elements.includes(name) ? false : (elements.push(name), true))
|
results.filter(({name}) => elements.includes(name) ? false : (elements.push(name), true))
|
||||||
return results.sort((a, b) => b.percent - a.percent)
|
return results.sort((a, b) => b.percent - a.percent)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
sections: options["wakatime.sections"].split(",").map(x => x.trim()).filter(x => x),
|
sections: options["wakatime.sections"].split(",").map(x => x.trim()).filter(x => x),
|
||||||
days: Number(options["wakatime.days"]) || 7,
|
days: Number(options["wakatime.days"]) || 7,
|
||||||
time: { total: faker.datatype.number(100000), daily: faker.datatype.number(24) },
|
time: {total: faker.datatype.number(100000), daily: faker.datatype.number(24)},
|
||||||
editors: stats(["VS Code", "Chrome", "IntelliJ", "PhpStorm", "WebStorm", "Android Studio", "Visual Studio", "Sublime Text", "PyCharm", "Vim", "Atom", "Xcode"]),
|
editors: stats(["VS Code", "Chrome", "IntelliJ", "PhpStorm", "WebStorm", "Android Studio", "Visual Studio", "Sublime Text", "PyCharm", "Vim", "Atom", "Xcode"]),
|
||||||
languages: stats(["JavaScript", "TypeScript", "PHP", "Java", "Python", "Vue.js", "HTML", "C#", "JSON", "Dart", "SCSS", "Kotlin", "JSX", "Go", "Ruby", "YAML"]),
|
languages: stats(["JavaScript", "TypeScript", "PHP", "Java", "Python", "Vue.js", "HTML", "C#", "JSON", "Dart", "SCSS", "Kotlin", "JSX", "Go", "Ruby", "YAML"]),
|
||||||
projects: stats(),
|
projects: stats(),
|
||||||
@@ -909,16 +909,16 @@
|
|||||||
count: faker.datatype.number(1000),
|
count: faker.datatype.number(1000),
|
||||||
minutesWatched: faker.datatype.number(100000),
|
minutesWatched: faker.datatype.number(100000),
|
||||||
episodesWatched: faker.datatype.number(10000),
|
episodesWatched: faker.datatype.number(10000),
|
||||||
genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })),
|
genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})),
|
||||||
},
|
},
|
||||||
manga: {
|
manga: {
|
||||||
count: faker.datatype.number(1000),
|
count: faker.datatype.number(1000),
|
||||||
chaptersRead: faker.datatype.number(100000),
|
chaptersRead: faker.datatype.number(100000),
|
||||||
volumesRead: faker.datatype.number(10000),
|
volumesRead: faker.datatype.number(10000),
|
||||||
genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })),
|
genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })),
|
genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})),
|
||||||
},
|
},
|
||||||
get lists() {
|
get lists() {
|
||||||
const media = type => ({
|
const media = type => ({
|
||||||
@@ -929,7 +929,7 @@
|
|||||||
genres: new Array(6).fill(null).map(_ => faker.lorem.word()),
|
genres: new Array(6).fill(null).map(_ => faker.lorem.word()),
|
||||||
progress: faker.datatype.number(100),
|
progress: faker.datatype.number(100),
|
||||||
description: faker.lorem.paragraphs(),
|
description: faker.lorem.paragraphs(),
|
||||||
scores: { user: faker.datatype.number(100), community: faker.datatype.number(100) },
|
scores: {user: faker.datatype.number(100), community: faker.datatype.number(100)},
|
||||||
released: 100 + faker.datatype.number(1000),
|
released: 100 + faker.datatype.number(1000),
|
||||||
artwork: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
artwork: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
|
||||||
})
|
})
|
||||||
@@ -939,16 +939,16 @@
|
|||||||
...(medias.includes("anime")
|
...(medias.includes("anime")
|
||||||
? {
|
? {
|
||||||
anime: {
|
anime: {
|
||||||
...(sections.includes("watching") ? { watching: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME")) } : {}),
|
...(sections.includes("watching") ? {watching: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME"))} : {}),
|
||||||
...(sections.includes("favorites") ? { favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME")) } : {}),
|
...(sections.includes("favorites") ? {favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("ANIME"))} : {}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(medias.includes("manga")
|
...(medias.includes("manga")
|
||||||
? {
|
? {
|
||||||
manga: {
|
manga: {
|
||||||
...(sections.includes("reading") ? { reading: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA")) } : {}),
|
...(sections.includes("reading") ? {reading: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA"))} : {}),
|
||||||
...(sections.includes("favorites") ? { favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA")) } : {}),
|
...(sections.includes("favorites") ? {favorites: new Array(Number(options["anilist.limit"]) || 4).fill(null).map(_ => media("MANGA"))} : {}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
@@ -974,7 +974,7 @@
|
|||||||
repo: `${faker.random.word()}/${faker.random.word()}`,
|
repo: `${faker.random.word()}/${faker.random.word()}`,
|
||||||
size: 1,
|
size: 1,
|
||||||
branch: "master",
|
branch: "master",
|
||||||
commits: [{ sha: faker.git.shortSha(), message: faker.lorem.sentence() }],
|
commits: [{sha: faker.git.shortSha(), message: faker.lorem.sentence()}],
|
||||||
timestamp: faker.date.recent(),
|
timestamp: faker.date.recent(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1026,8 +1026,8 @@
|
|||||||
user: set.user,
|
user: set.user,
|
||||||
number: faker.datatype.number(100),
|
number: faker.datatype.number(100),
|
||||||
title: faker.lorem.sentence(),
|
title: faker.lorem.sentence(),
|
||||||
lines: { added: faker.datatype.number(1000), deleted: faker.datatype.number(1000) },
|
lines: {added: faker.datatype.number(1000), deleted: faker.datatype.number(1000)},
|
||||||
files: { changed: faker.datatype.number(10) },
|
files: {changed: faker.datatype.number(10)},
|
||||||
timestamp: faker.date.recent(),
|
timestamp: faker.date.recent(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1061,13 +1061,13 @@
|
|||||||
{
|
{
|
||||||
type: "ref/create",
|
type: "ref/create",
|
||||||
repo: `${faker.random.word()}/${faker.random.word()}`,
|
repo: `${faker.random.word()}/${faker.random.word()}`,
|
||||||
ref: { name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"]) },
|
ref: {name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"])},
|
||||||
timestamp: faker.date.recent(),
|
timestamp: faker.date.recent(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "ref/delete",
|
type: "ref/delete",
|
||||||
repo: `${faker.random.word()}/${faker.random.word()}`,
|
repo: `${faker.random.word()}/${faker.random.word()}`,
|
||||||
ref: { name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"]) },
|
ref: {name: faker.lorem.slug(), type: faker.random.arrayElement(["tag", "branch"])},
|
||||||
timestamp: faker.date.recent(),
|
timestamp: faker.date.recent(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1096,7 +1096,7 @@
|
|||||||
...(set.plugins.enabled.isocalendar
|
...(set.plugins.enabled.isocalendar
|
||||||
? ({
|
? ({
|
||||||
isocalendar: {
|
isocalendar: {
|
||||||
streak: { max: 30 + faker.datatype.number(20), current: faker.datatype.number(30) },
|
streak: {max: 30 + faker.datatype.number(20), current: faker.datatype.number(30)},
|
||||||
max: 10 + faker.datatype.number(40),
|
max: 10 + faker.datatype.number(40),
|
||||||
average: faker.datatype.float(10),
|
average: faker.datatype.float(10),
|
||||||
svg: await staticPlaceholder(set.plugins.enabled.isocalendar, `isocalendar.${options["isocalendar.duration"]}.svg`),
|
svg: await staticPlaceholder(set.plugins.enabled.isocalendar, `isocalendar.${options["isocalendar.duration"]}.svg`),
|
||||||
@@ -1108,8 +1108,8 @@
|
|||||||
...(set.plugins.enabled.support
|
...(set.plugins.enabled.support
|
||||||
? ({
|
? ({
|
||||||
support: {
|
support: {
|
||||||
stats: { solutions: faker.datatype.number(100), posts: faker.datatype.number(1000), topics: faker.datatype.number(1000), received: faker.datatype.number(1000), hearts: faker.datatype.number(1000) },
|
stats: {solutions: faker.datatype.number(100), posts: faker.datatype.number(1000), topics: faker.datatype.number(1000), received: faker.datatype.number(1000), hearts: faker.datatype.number(1000)},
|
||||||
badges: { uniques: [], multiples: [], count: faker.datatype.number(1000) },
|
badges: {uniques: [], multiples: [], count: faker.datatype.number(1000)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
: null),
|
: null),
|
||||||
@@ -1200,20 +1200,20 @@
|
|||||||
}
|
}
|
||||||
//Formatters
|
//Formatters
|
||||||
data.f.bytes = function(n) {
|
data.f.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)
|
if (n / v >= 1)
|
||||||
return `${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B`
|
return `${(n / v).toFixed(2).substr(0, 4).replace(/[.]0*$/, "")} ${u}B`
|
||||||
}
|
}
|
||||||
return `${n} byte${n > 1 ? "s" : ""}`
|
return `${n} byte${n > 1 ? "s" : ""}`
|
||||||
}
|
}
|
||||||
data.f.percentage = function(n, { rescale = true } = {}) {
|
data.f.percentage = function(n, {rescale = true} = {}) {
|
||||||
return `${
|
return `${
|
||||||
(n * (rescale ? 100 : 1)).toFixed(2)
|
(n * (rescale ? 100 : 1)).toFixed(2)
|
||||||
.replace(/[.]([1-9]*)(0+)$/, (m, a, b) => `.${a}`)
|
.replace(/[.]([1-9]*)(0+)$/, (m, a, b) => `.${a}`)
|
||||||
.replace(/[.]$/, "")
|
.replace(/[.]$/, "")
|
||||||
}%`
|
}%`
|
||||||
}
|
}
|
||||||
data.f.ellipsis = function(text, { length = 20 } = {}) {
|
data.f.ellipsis = function(text, {length = 20} = {}) {
|
||||||
text = `${text}`
|
text = `${text}`
|
||||||
if (text.length < length)
|
if (text.length < length)
|
||||||
return text
|
return text
|
||||||
@@ -1222,11 +1222,11 @@
|
|||||||
data.f.date = function(string, options) {
|
data.f.date = function(string, options) {
|
||||||
if (options.date) {
|
if (options.date) {
|
||||||
delete 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) {
|
if (options.time) {
|
||||||
delete 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", options).format(new Date(string))
|
return new Intl.DateTimeFormat("en-GB", options).format(new Date(string))
|
||||||
}
|
}
|
||||||
@@ -1234,7 +1234,7 @@
|
|||||||
return text?.name ?? text
|
return text?.name ?? text
|
||||||
}
|
}
|
||||||
//Render
|
//Render
|
||||||
return await ejs.render(image, data, { async: true, rmWhitespace: true })
|
return await ejs.render(image, data, {async: true, rmWhitespace: true})
|
||||||
}
|
}
|
||||||
//Reset globals contexts
|
//Reset globals contexts
|
||||||
globalThis.placeholder.init = function(globals) {
|
globalThis.placeholder.init = function(globals) {
|
||||||
|
|||||||
@@ -18,48 +18,48 @@ export default async function({login, q, imports, data, computed, graphql, queri
|
|||||||
await compute[account]({list, login, data, computed, imports, graphql, queries, rank, leaderboard})
|
await compute[account]({list, login, data, computed, imports, graphql, queries, rank, leaderboard})
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
const order = {S:5, A:4, B:3, C:2, $:1, X:0}
|
const order = {S: 5, A: 4, B: 3, C: 2, $: 1, X: 0}
|
||||||
const colors = {S:["#EB355E", "#731237"], A:["#B59151", "#FFD576"], B:["#7D6CFF", "#B2A8FF"], C:["#2088FF", "#79B8FF"], $:["#FF48BD", "#FF92D8"], X:["#7A7A7A", "#B0B0B0"]}
|
const colors = {S: ["#EB355E", "#731237"], A: ["#B59151", "#FFD576"], B: ["#7D6CFF", "#B2A8FF"], C: ["#2088FF", "#79B8FF"], $: ["#FF48BD", "#FF92D8"], X: ["#7A7A7A", "#B0B0B0"]}
|
||||||
const achievements = list
|
const achievements = list
|
||||||
.filter(a => (order[a.rank] >= order[threshold]) || ((a.rank === "$") && (secrets)))
|
.filter(a => (order[a.rank] >= order[threshold]) || ((a.rank === "$") && (secrets)))
|
||||||
.filter(a => (!only.length) || ((only.length) && (only.includes(a.title.toLocaleLowerCase()))))
|
.filter(a => (!only.length) || ((only.length) && (only.includes(a.title.toLocaleLowerCase()))))
|
||||||
.filter(a => !ignored.includes(a.title.toLocaleLowerCase()))
|
.filter(a => !ignored.includes(a.title.toLocaleLowerCase()))
|
||||||
.sort((a, b) => (order[b.rank] + b.progress * 0.99) - (order[a.rank] + a.progress * 0.99))
|
.sort((a, b) => (order[b.rank] + b.progress * 0.99) - (order[a.rank] + a.progress * 0.99))
|
||||||
.map(({title, unlock, ...achievement}) => ({
|
.map(({title, unlock, ...achievement}) => ({
|
||||||
prefix:({S:"Master", A:"Super", B:"Great"}[achievement.rank] ?? ""),
|
prefix: ({S: "Master", A: "Super", B: "Great"}[achievement.rank] ?? ""),
|
||||||
title,
|
title,
|
||||||
unlock:!/invalid date/i.test(unlock) ? `${imports.format.date(unlock, {time:true})} on ${imports.format.date(unlock, {date:true})}` : null,
|
unlock: !/invalid date/i.test(unlock) ? `${imports.format.date(unlock, {time: true})} on ${imports.format.date(unlock, {date: true})}` : null,
|
||||||
...achievement,
|
...achievement,
|
||||||
}))
|
}))
|
||||||
.map(({icon, ...achievement}) => ({icon:icon.replace(/#primary/g, colors[achievement.rank][0]).replace(/#secondary/g, colors[achievement.rank][1]), ...achievement}))
|
.map(({icon, ...achievement}) => ({icon: icon.replace(/#primary/g, colors[achievement.rank][0]).replace(/#secondary/g, colors[achievement.rank][1]), ...achievement}))
|
||||||
.slice(0, limit || Infinity)
|
.slice(0, limit || Infinity)
|
||||||
return {list:achievements, display}
|
return {list: achievements, display}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Rank */
|
/**Rank */
|
||||||
function rank(x, [c, b, a, s, m]) {
|
function rank(x, [c, b, a, s, m]) {
|
||||||
if (x >= s)
|
if (x >= s)
|
||||||
return {rank:"S", progress:(x - s) / (m - s)}
|
return {rank: "S", progress: (x - s) / (m - s)}
|
||||||
if (x >= a)
|
if (x >= a)
|
||||||
return {rank:"A", progress:(x - a) / (m - a)}
|
return {rank: "A", progress: (x - a) / (m - a)}
|
||||||
else if (x >= b)
|
else if (x >= b)
|
||||||
return {rank:"B", progress:(x - b) / (a - b)}
|
return {rank: "B", progress: (x - b) / (a - b)}
|
||||||
else if (x >= c)
|
else if (x >= c)
|
||||||
return {rank:"C", progress:(x - c) / (b - c)}
|
return {rank: "C", progress: (x - c) / (b - c)}
|
||||||
return {rank:"X", progress:x / c}
|
return {rank: "X", progress: x / c}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Leaderboards */
|
/**Leaderboards */
|
||||||
function leaderboard({user, type, requirement}) {
|
function leaderboard({user, type, requirement}) {
|
||||||
return requirement
|
return requirement
|
||||||
? {
|
? {
|
||||||
user:1 + user,
|
user: 1 + user,
|
||||||
total:total[type],
|
total: total[type],
|
||||||
type,
|
type,
|
||||||
get top() {
|
get top() {
|
||||||
return Number(`1${"0".repeat(Math.ceil(Math.log10(this.user)))}`)
|
return Number(`1${"0".repeat(Math.ceil(Math.log10(this.user)))}`)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
//Exports
|
//Exports
|
||||||
export {default as organization} from "./organizations.mjs"
|
export { default as organization } from "./organizations.mjs"
|
||||||
export {default as user} from "./users.mjs"
|
export { default as user } from "./users.mjs"
|
||||||
|
|||||||
@@ -2,22 +2,23 @@
|
|||||||
export default async function({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) {
|
export default async function({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) {
|
||||||
//Initialization
|
//Initialization
|
||||||
const {organization} = await graphql(queries.achievements.organizations({login}))
|
const {organization} = await graphql(queries.achievements.organizations({login}))
|
||||||
const scores = {followers:0, created:organization.repositories.totalCount, stars:organization.popular.nodes?.[0]?.stargazers?.totalCount ?? 0, forks:Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))}
|
const scores = {followers: 0, created: organization.repositories.totalCount, stars: organization.popular.nodes?.[0]?.stargazers?.totalCount ?? 0, forks: Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))}
|
||||||
const ranks = await graphql(queries.achievements.ranking(scores))
|
const ranks = await graphql(queries.achievements.ranking(scores))
|
||||||
const requirements = {stars:5, followers:3, forks:1, created:1}
|
const requirements = {stars: 5, followers: 3, forks: 1, created: 1}
|
||||||
|
|
||||||
//Developers
|
//Developers
|
||||||
{
|
{
|
||||||
const value = organization.repositories.totalCount
|
const value = organization.repositories.totalCount
|
||||||
const unlock = organization.repositories.nodes?.shift()
|
const unlock = organization.repositories.nodes?.shift()
|
||||||
list.push({
|
list.push({
|
||||||
title:"Developers",
|
title: "Developers",
|
||||||
text:`Published ${value} public repositor${imports.s(value, "y")}`,
|
text: `Published ${value} public repositor${imports.s(value, "y")}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#primary"><path d="M20 24l-3.397 3.398a.85.85 0 000 1.203L20.002 32M37.015 24l3.399 3.398a.85.85 0 010 1.203L37.014 32" stroke-linejoin="round"/><path d="M31.029 21.044L25.976 35.06"/></g><path stroke="#secondary" stroke-linejoin="round" d="M23.018 10h8.984M26 47h5M8 16h16m9 0h15.725M8 41h13"/><path d="M5.027 34.998c.673 2.157 1.726 4.396 2.81 6.02m43.38-19.095C50.7 19.921 49.866 17.796 48.79 16" stroke="#secondary"/><path stroke="#primary" stroke-linejoin="round" d="M26 41h17"/><path d="M7.183 16C5.186 19.582 4 23.619 4 28M42.608 47.02c2.647-1.87 5.642-5.448 7.295-9.18C51.52 34.191 52.071 30.323 52 28" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M7.226 16H28M13.343 47H21"/><path d="M13.337 47.01a24.364 24.364 0 006.19 3.45 24.527 24.527 0 007.217 1.505c2.145.108 4.672-.05 7.295-.738" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M36 47h6.647M12 10h6M37 10h6.858"/><path d="M43.852 10c-4.003-3.667-9.984-6.054-16.047-6-2.367.021-4.658.347-6.81 1.045" stroke="#primary"/><path stroke="#secondary" stroke-linejoin="round" d="M5.041 35h4.962M47 22h4.191"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#primary"><path d="M20 24l-3.397 3.398a.85.85 0 000 1.203L20.002 32M37.015 24l3.399 3.398a.85.85 0 010 1.203L37.014 32" stroke-linejoin="round"/><path d="M31.029 21.044L25.976 35.06"/></g><path stroke="#secondary" stroke-linejoin="round" d="M23.018 10h8.984M26 47h5M8 16h16m9 0h15.725M8 41h13"/><path d="M5.027 34.998c.673 2.157 1.726 4.396 2.81 6.02m43.38-19.095C50.7 19.921 49.866 17.796 48.79 16" stroke="#secondary"/><path stroke="#primary" stroke-linejoin="round" d="M26 41h17"/><path d="M7.183 16C5.186 19.582 4 23.619 4 28M42.608 47.02c2.647-1.87 5.642-5.448 7.295-9.18C51.52 34.191 52.071 30.323 52 28" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M7.226 16H28M13.343 47H21"/><path d="M13.337 47.01a24.364 24.364 0 006.19 3.45 24.527 24.527 0 007.217 1.505c2.145.108 4.672-.05 7.295-.738" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M36 47h6.647M12 10h6M37 10h6.858"/><path d="M43.852 10c-4.003-3.667-9.984-6.054-16.047-6-2.367.021-4.658.347-6.81 1.045" stroke="#primary"/><path stroke="#secondary" stroke-linejoin="round" d="M5.041 35h4.962M47 22h4.191"/></g>',
|
||||||
...rank(value, [1, 50, 100, 200, 300]),
|
...rank(value, [1, 50, 100, 200, 300]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.created_rank.userCount, requirement:scores.created >= requirements.created, type:"users"}),
|
leaderboard: leaderboard({user: ranks.created_rank.userCount, requirement: scores.created >= requirements.created, type: "users"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +27,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const value = organization.forks.totalCount
|
const value = organization.forks.totalCount
|
||||||
const unlock = organization.forks.nodes?.shift()
|
const unlock = organization.forks.nodes?.shift()
|
||||||
list.push({
|
list.push({
|
||||||
title:"Forkers",
|
title: "Forkers",
|
||||||
text:`Forked ${value} public repositor${imports.s(value, "y")}`,
|
text: `Forked ${value} public repositor${imports.s(value, "y")}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M37.303 21.591a5.84 5.84 0 00-1.877-1.177 6.138 6.138 0 00-4.432 0 5.822 5.822 0 00-1.879 1.177L28 22.638l-1.115-1.047c-1.086-1.018-2.559-1.59-4.094-1.59-1.536 0-3.008.572-4.094 1.59-1.086 1.02-1.696 2.4-1.696 3.84 0 1.441.61 2.823 1.696 3.841l1.115 1.046L28 38l8.189-7.682 1.115-1.046a5.422 5.422 0 001.256-1.761 5.126 5.126 0 000-4.157 5.426 5.426 0 00-1.256-1.763z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.967 42.705A18.922 18.922 0 0028 47a18.92 18.92 0 0011.076-3.56m-.032-30.902A18.914 18.914 0 0028 9c-4.09 0-7.876 1.292-10.976 3.49" stroke="#secondary" stroke-linecap="round"/><g transform="translate(7 10)" stroke="#primary"><path d="M6 0v7c0 2.21-1.343 3-3 3s-3-.79-3-3V0" stroke-linecap="round" stroke-linejoin="round"/><path stroke-linecap="round" d="M3 0v19.675"/><rect stroke-linejoin="round" x="1" y="20" width="4" height="16" rx="2"/></g><g transform="translate(43 10)" stroke="#primary"><path stroke-linecap="round" d="M2 15.968v3.674"/><path d="M4 15.642H0L.014 4.045A4.05 4.05 0 014.028 0L4 15.642z" stroke-linecap="round" stroke-linejoin="round"/><rect stroke-linejoin="round" y="19.968" width="4" height="16" rx="2"/></g><path d="M41.364 8.062A23.888 23.888 0 0028 4a23.89 23.89 0 00-11.95 3.182M4.75 22.021A24.045 24.045 0 004 28c0 1.723.182 3.404.527 5.024m10.195 14.971A23.888 23.888 0 0028 52c4.893 0 9.444-1.464 13.239-3.979m9-10.98A23.932 23.932 0 0052 28c0-2.792-.477-5.472-1.353-7.964" stroke="#secondary" stroke-linecap="round"/></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M37.303 21.591a5.84 5.84 0 00-1.877-1.177 6.138 6.138 0 00-4.432 0 5.822 5.822 0 00-1.879 1.177L28 22.638l-1.115-1.047c-1.086-1.018-2.559-1.59-4.094-1.59-1.536 0-3.008.572-4.094 1.59-1.086 1.02-1.696 2.4-1.696 3.84 0 1.441.61 2.823 1.696 3.841l1.115 1.046L28 38l8.189-7.682 1.115-1.046a5.422 5.422 0 001.256-1.761 5.126 5.126 0 000-4.157 5.426 5.426 0 00-1.256-1.763z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.967 42.705A18.922 18.922 0 0028 47a18.92 18.92 0 0011.076-3.56m-.032-30.902A18.914 18.914 0 0028 9c-4.09 0-7.876 1.292-10.976 3.49" stroke="#secondary" stroke-linecap="round"/><g transform="translate(7 10)" stroke="#primary"><path d="M6 0v7c0 2.21-1.343 3-3 3s-3-.79-3-3V0" stroke-linecap="round" stroke-linejoin="round"/><path stroke-linecap="round" d="M3 0v19.675"/><rect stroke-linejoin="round" x="1" y="20" width="4" height="16" rx="2"/></g><g transform="translate(43 10)" stroke="#primary"><path stroke-linecap="round" d="M2 15.968v3.674"/><path d="M4 15.642H0L.014 4.045A4.05 4.05 0 014.028 0L4 15.642z" stroke-linecap="round" stroke-linejoin="round"/><rect stroke-linejoin="round" y="19.968" width="4" height="16" rx="2"/></g><path d="M41.364 8.062A23.888 23.888 0 0028 4a23.89 23.89 0 00-11.95 3.182M4.75 22.021A24.045 24.045 0 004 28c0 1.723.182 3.404.527 5.024m10.195 14.971A23.888 23.888 0 0028 52c4.893 0 9.444-1.464 13.239-3.979m9-10.98A23.932 23.932 0 0052 28c0-2.792-.477-5.472-1.353-7.964" stroke="#secondary" stroke-linecap="round"/></g>',
|
||||||
...rank(value, [1, 10, 30, 50, 100]),
|
...rank(value, [1, 10, 30, 50, 100]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,12 +43,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = organization.projects.nodes?.shift()
|
const unlock = organization.projects.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Managers",
|
title: "Managers",
|
||||||
text:`Created ${value} user project${imports.s(value)}`,
|
text: `Created ${value} user project${imports.s(value)}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M29 16V8.867C29 7.705 29.627 7 30.692 7h18.616C50.373 7 51 7.705 51 8.867v38.266C51 48.295 50.373 49 49.308 49H30.692C29.627 49 29 48.295 29 47.133V39m-4-23V9c0-1.253-.737-2-2-2H7c-1.263 0-2 .747-2 2v34c0 1.253.737 2 2 2h16c1.263 0 2-.747 2-2v-4" stroke="#secondary" stroke-linecap="round"/><path stroke="#secondary" d="M51.557 12.005h-22M5 12.005h21"/><path d="M14 33V22c0-1.246.649-2 1.73-2h28.54c1.081 0 1.73.754 1.73 2v11c0 1.246-.649 2-1.73 2H15.73c-1.081 0-1.73-.754-1.73-2z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 29v-3c0-.508.492-1 1-1h3c.508 0 1 .492 1 1v3c0 .508-.492 1-1 1h-3c-.508-.082-1-.492-1-1z" stroke="#primary"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M28.996 27.998h12M9.065 20.04a7.062 7.062 0 00-.023 1.728m.775 2.517c.264.495.584.954.954 1.369"/></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M29 16V8.867C29 7.705 29.627 7 30.692 7h18.616C50.373 7 51 7.705 51 8.867v38.266C51 48.295 50.373 49 49.308 49H30.692C29.627 49 29 48.295 29 47.133V39m-4-23V9c0-1.253-.737-2-2-2H7c-1.263 0-2 .747-2 2v34c0 1.253.737 2 2 2h16c1.263 0 2-.747 2-2v-4" stroke="#secondary" stroke-linecap="round"/><path stroke="#secondary" d="M51.557 12.005h-22M5 12.005h21"/><path d="M14 33V22c0-1.246.649-2 1.73-2h28.54c1.081 0 1.73.754 1.73 2v11c0 1.246-.649 2-1.73 2H15.73c-1.081 0-1.73-.754-1.73-2z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 29v-3c0-.508.492-1 1-1h3c.508 0 1 .492 1 1v3c0 .508-.492 1-1 1h-3c-.508-.082-1-.492-1-1z" stroke="#primary"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M28.996 27.998h12M9.065 20.04a7.062 7.062 0 00-.023 1.728m.775 2.517c.264.495.584.954.954 1.369"/></g>',
|
||||||
...rank(value, [1, 2, 4, 8, 10]),
|
...rank(value, [1, 2, 4, 8, 10]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +59,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = organization.packages.nodes?.shift()
|
const unlock = organization.packages.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Packagers",
|
title: "Packagers",
|
||||||
text:`Created ${value} package${imports.s(value)}`,
|
text: `Created ${value} package${imports.s(value)}`,
|
||||||
icon:'<g fill="none"><path fill="#secondary" d="M28.53 27.64l-11.2 6.49V21.15l11.23-6.48z"/><path d="M40.4 34.84c-.17 0-.34-.04-.5-.13l-11.24-6.44a.99.99 0 01-.37-1.36.99.99 0 011.36-.37l11.24 6.44c.48.27.65.89.37 1.36-.17.32-.51.5-.86.5z" fill="#primary"/><path d="M29.16 28.4c-.56 0-1-.45-1-1.01l.08-12.47c0-.55.49-1 1.01-.99.55 0 1 .45.99 1.01l-.08 12.47c0 .55-.45.99-1 .99z" fill="#primary"/><path d="M18.25 34.65a.996.996 0 01-.5-1.86l10.91-6.25a.997.997 0 11.99 1.73l-10.91 6.25c-.15.09-.32.13-.49.13z" fill="#primary"/><path d="M29.19 41.37c-.17 0-.35-.04-.5-.13l-11.23-6.49c-.31-.18-.5-.51-.5-.87V20.91c0-.36.19-.69.5-.87l11.23-6.49c.31-.18.69-.18 1 0l11.23 6.49c.31.18.5.51.5.87v12.97c0 .36-.19.69-.5.87l-11.23 6.49c-.15.08-.32.13-.5.13zm-10.23-8.06l10.23 5.91 10.23-5.91V21.49l-10.23-5.91-10.23 5.91v11.82zM40.5 11.02c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.19 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm23.37 43.8c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.35c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.06 4.11c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zM6.18 30.72C4.43 30.72 3 29.29 3 27.54c0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm45.64 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18z" fill="#primary"/><path d="M29.1 10.21c-.55 0-1-.45-1-1V3.52c0-.55.45-1 1-1s1 .45 1 1v5.69c0 .56-.45 1-1 1zM7.44 20.95c-.73 0-1.32-.59-1.32-1.32v-5.38l4.66-2.69c.63-.37 1.44-.15 1.8.48.36.63.15 1.44-.48 1.8l-3.34 1.93v3.86c0 .73-.59 1.32-1.32 1.32zm4 22.68c-.22 0-.45-.06-.66-.18l-4.66-2.69v-5.38c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v3.86l3.34 1.93c.63.36.85 1.17.48 1.8-.24.42-.68.66-1.14.66zm17.64 10.39l-4.66-2.69c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l3.34 1.93 3.34-1.93a1.32 1.32 0 011.8.48c.36.63.15 1.44-.48 1.8l-4.66 2.69zm17.64-10.39a1.32 1.32 0 01-.66-2.46l3.34-1.93v-3.86c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v5.38l-4.66 2.69c-.21.12-.44.18-.66.18zm4-22.68c-.73 0-1.32-.59-1.32-1.32v-3.86l-3.34-1.93c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l4.66 2.69v5.38c0 .73-.59 1.32-1.32 1.32z" fill="#secondary"/><path d="M33.08 6.15c-.22 0-.45-.06-.66-.18l-3.34-1.93-3.34 1.93c-.63.36-1.44.15-1.8-.48a1.32 1.32 0 01.48-1.8L29.08 1l4.66 2.69c.63.36.85 1.17.48 1.8a1.3 1.3 0 01-1.14.66zm-3.99 47.3c-.55 0-1-.45-1-1v-7.13c0-.55.45-1 1-1s1 .45 1 1v7.13c0 .55-.44 1-1 1zM13.86 19.71c-.17 0-.34-.04-.5-.13L7.2 16a1 1 0 011-1.73l6.17 3.58c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zm36.63 21.23c-.17 0-.34-.04-.5-.13l-6.17-3.57a.998.998 0 01-.36-1.37c.28-.48.89-.64 1.37-.36L51 39.08c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zM44.06 19.8c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.16.1-.33.14-.5.14zM7.43 41.03c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.15.09-.33.14-.5.14z" fill="#secondary"/></g>',
|
icon:
|
||||||
|
'<g fill="none"><path fill="#secondary" d="M28.53 27.64l-11.2 6.49V21.15l11.23-6.48z"/><path d="M40.4 34.84c-.17 0-.34-.04-.5-.13l-11.24-6.44a.99.99 0 01-.37-1.36.99.99 0 011.36-.37l11.24 6.44c.48.27.65.89.37 1.36-.17.32-.51.5-.86.5z" fill="#primary"/><path d="M29.16 28.4c-.56 0-1-.45-1-1.01l.08-12.47c0-.55.49-1 1.01-.99.55 0 1 .45.99 1.01l-.08 12.47c0 .55-.45.99-1 .99z" fill="#primary"/><path d="M18.25 34.65a.996.996 0 01-.5-1.86l10.91-6.25a.997.997 0 11.99 1.73l-10.91 6.25c-.15.09-.32.13-.49.13z" fill="#primary"/><path d="M29.19 41.37c-.17 0-.35-.04-.5-.13l-11.23-6.49c-.31-.18-.5-.51-.5-.87V20.91c0-.36.19-.69.5-.87l11.23-6.49c.31-.18.69-.18 1 0l11.23 6.49c.31.18.5.51.5.87v12.97c0 .36-.19.69-.5.87l-11.23 6.49c-.15.08-.32.13-.5.13zm-10.23-8.06l10.23 5.91 10.23-5.91V21.49l-10.23-5.91-10.23 5.91v11.82zM40.5 11.02c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.19 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm23.37 43.8c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.35c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.06 4.11c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zM6.18 30.72C4.43 30.72 3 29.29 3 27.54c0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm45.64 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18z" fill="#primary"/><path d="M29.1 10.21c-.55 0-1-.45-1-1V3.52c0-.55.45-1 1-1s1 .45 1 1v5.69c0 .56-.45 1-1 1zM7.44 20.95c-.73 0-1.32-.59-1.32-1.32v-5.38l4.66-2.69c.63-.37 1.44-.15 1.8.48.36.63.15 1.44-.48 1.8l-3.34 1.93v3.86c0 .73-.59 1.32-1.32 1.32zm4 22.68c-.22 0-.45-.06-.66-.18l-4.66-2.69v-5.38c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v3.86l3.34 1.93c.63.36.85 1.17.48 1.8-.24.42-.68.66-1.14.66zm17.64 10.39l-4.66-2.69c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l3.34 1.93 3.34-1.93a1.32 1.32 0 011.8.48c.36.63.15 1.44-.48 1.8l-4.66 2.69zm17.64-10.39a1.32 1.32 0 01-.66-2.46l3.34-1.93v-3.86c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v5.38l-4.66 2.69c-.21.12-.44.18-.66.18zm4-22.68c-.73 0-1.32-.59-1.32-1.32v-3.86l-3.34-1.93c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l4.66 2.69v5.38c0 .73-.59 1.32-1.32 1.32z" fill="#secondary"/><path d="M33.08 6.15c-.22 0-.45-.06-.66-.18l-3.34-1.93-3.34 1.93c-.63.36-1.44.15-1.8-.48a1.32 1.32 0 01.48-1.8L29.08 1l4.66 2.69c.63.36.85 1.17.48 1.8a1.3 1.3 0 01-1.14.66zm-3.99 47.3c-.55 0-1-.45-1-1v-7.13c0-.55.45-1 1-1s1 .45 1 1v7.13c0 .55-.44 1-1 1zM13.86 19.71c-.17 0-.34-.04-.5-.13L7.2 16a1 1 0 011-1.73l6.17 3.58c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zm36.63 21.23c-.17 0-.34-.04-.5-.13l-6.17-3.57a.998.998 0 01-.36-1.37c.28-.48.89-.64 1.37-.36L51 39.08c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zM44.06 19.8c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.16.1-.33.14-.5.14zM7.43 41.03c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.15.09-.33.14-.5.14z" fill="#secondary"/></g>',
|
||||||
...rank(value, [1, 20, 50, 100, 250]),
|
...rank(value, [1, 20, 50, 100, 250]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,13 +75,14 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Maintainers",
|
title: "Maintainers",
|
||||||
text:`Maintaining a repository with ${value} star${imports.s(value)}`,
|
text: `Maintaining a repository with ${value} star${imports.s(value)}`,
|
||||||
icon:'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M39 15h.96l4.038 3-.02-3H45a2 2 0 002-2V3a2 2 0 00-2-2H31a2 2 0 00-2 2v4.035" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M36 5.014l-3 3 3 3M40 5.014l3 3-3 3"/><path d="M6 37a1 1 0 110 2 1 1 0 010-2m7 0a1 1 0 110 2 1 1 0 010-2m-2.448 1a1 1 0 11-2 0 1 1 0 012 0z" fill="#primary"/><path d="M1.724 15.05A23.934 23.934 0 000 24c0 .686.029 1.366.085 2.037m19.92 21.632c1.3.218 2.634.331 3.995.331a23.92 23.92 0 009.036-1.76m13.207-13.21A23.932 23.932 0 0048 24c0-1.363-.114-2.7-.332-4M25.064.022a23.932 23.932 0 00-10.073 1.725" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M19 42.062V43a2 2 0 01-2 2H9.04l-4.038 3 .02-3H3a2 2 0 01-2-2V33a2 2 0 012-2h4.045" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 0a6 6 0 110 12A6 6 0 016 0z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" d="M6 3v6M3 6h6"/><path d="M42 36a6 6 0 110 12 6 6 0 010-12z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M44.338 40.663l-3.336 3.331-1.692-1.686M31 31c-.716-2.865-3.578-5-7-5-3.423 0-6.287 2.14-7 5"/><path d="M24 16a5 5 0 110 10 5 5 0 010-10z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><circle stroke="#primary" stroke-width="2" cx="24" cy="24" r="14"/></g>',
|
icon:
|
||||||
|
'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M39 15h.96l4.038 3-.02-3H45a2 2 0 002-2V3a2 2 0 00-2-2H31a2 2 0 00-2 2v4.035" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M36 5.014l-3 3 3 3M40 5.014l3 3-3 3"/><path d="M6 37a1 1 0 110 2 1 1 0 010-2m7 0a1 1 0 110 2 1 1 0 010-2m-2.448 1a1 1 0 11-2 0 1 1 0 012 0z" fill="#primary"/><path d="M1.724 15.05A23.934 23.934 0 000 24c0 .686.029 1.366.085 2.037m19.92 21.632c1.3.218 2.634.331 3.995.331a23.92 23.92 0 009.036-1.76m13.207-13.21A23.932 23.932 0 0048 24c0-1.363-.114-2.7-.332-4M25.064.022a23.932 23.932 0 00-10.073 1.725" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M19 42.062V43a2 2 0 01-2 2H9.04l-4.038 3 .02-3H3a2 2 0 01-2-2V33a2 2 0 012-2h4.045" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 0a6 6 0 110 12A6 6 0 016 0z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" d="M6 3v6M3 6h6"/><path d="M42 36a6 6 0 110 12 6 6 0 010-12z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M44.338 40.663l-3.336 3.331-1.692-1.686M31 31c-.716-2.865-3.578-5-7-5-3.423 0-6.287 2.14-7 5"/><path d="M24 16a5 5 0 110 10 5 5 0 010-10z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><circle stroke="#primary" stroke-width="2" cx="24" cy="24" r="14"/></g>',
|
||||||
...rank(value, [1, 5000, 10000, 30000, 50000]),
|
...rank(value, [1, 5000, 10000, 30000, 50000]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.repo_rank.repositoryCount, requirement:scores.stars >= requirements.stars, type:"repositories"}),
|
leaderboard: leaderboard({user: ranks.repo_rank.repositoryCount, requirement: scores.stars >= requirements.stars, type: "repositories"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,28 +91,30 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))
|
const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))
|
||||||
const unlock = null
|
const unlock = null
|
||||||
list.push({
|
list.push({
|
||||||
title:"Inspirers",
|
title: "Inspirers",
|
||||||
text:`Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`,
|
text: `Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`,
|
||||||
icon:'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M20.065 47.122c.44-.525.58-1.448.58-1.889 0-2.204-1.483-3.967-3.633-4.187.447-1.537.58-2.64.397-3.31-.25-.92-.745-1.646-1.409-2.235m-5.97-7.157c.371-.254.911-.748 1.62-1.48a8.662 8.662 0 001.432-2.366M47 22h-7c-1.538 0-2.749-.357-4-1h-5c-1.789.001-3-1.3-3-2.955 0-1.656 1.211-3.04 3-3.045h2c.027-1.129.513-2.17 1-3m3.082 32.004C34.545 43.028 34.02 40.569 34 39v-1h-1c-2.603-.318-5-2.913-5-5.997S30.397 26 33 26h9c2.384 0 4.326 1.024 5.27 3" stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><g transform="translate(36)" stroke="#primary" stroke-width="2"><path fill="#primary" stroke-linecap="round" stroke-linejoin="round" d="M5.395 5.352L6.009 4l.598 1.348L8 5.408l-1.067 1.12.425 1.47-1.356-.908-1.35.91.404-1.469L4 5.41z"/><circle cx="6" cy="6" r="6"/></g><g transform="translate(0 31)" stroke="#primary" stroke-width="2"><circle cx="6" cy="6" r="6"/><g stroke-linecap="round"><path d="M6 4v4M4 6h4"/></g></g><circle stroke="#primary" stroke-width="2" cx="10.5" cy="10.5" r="10.5"/><g stroke-linecap="round"><path d="M32.01 1.37A23.96 23.96 0 0024 0c-.999 0-1.983.061-2.95.18M.32 20.072a24.21 24.21 0 00.015 7.948M12.42 45.025A23.892 23.892 0 0024 48c13.255 0 24-10.745 24-24 0-2.811-.483-5.51-1.371-8.016" stroke="#secondary" stroke-width="2"/><path stroke="#primary" stroke-width="2" d="M8.999 7.151v5.865"/><path d="M9 3a2 2 0 110 4 2 2 0 010-4zm0 10.8a2 2 0 11-.001 4 2 2 0 01.001-4z" stroke="#primary" stroke-width="1.8"/><path d="M9.622 11.838c.138-.007.989.119 1.595-.05.607-.169 1.584-.539 1.829-1.337" stroke="#primary" stroke-width="2"/><path d="M14.8 7.202a2 2 0 110 4 2 2 0 010-4z" stroke="#primary" stroke-width="1.8"/></g></g>',
|
icon:
|
||||||
|
'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M20.065 47.122c.44-.525.58-1.448.58-1.889 0-2.204-1.483-3.967-3.633-4.187.447-1.537.58-2.64.397-3.31-.25-.92-.745-1.646-1.409-2.235m-5.97-7.157c.371-.254.911-.748 1.62-1.48a8.662 8.662 0 001.432-2.366M47 22h-7c-1.538 0-2.749-.357-4-1h-5c-1.789.001-3-1.3-3-2.955 0-1.656 1.211-3.04 3-3.045h2c.027-1.129.513-2.17 1-3m3.082 32.004C34.545 43.028 34.02 40.569 34 39v-1h-1c-2.603-.318-5-2.913-5-5.997S30.397 26 33 26h9c2.384 0 4.326 1.024 5.27 3" stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><g transform="translate(36)" stroke="#primary" stroke-width="2"><path fill="#primary" stroke-linecap="round" stroke-linejoin="round" d="M5.395 5.352L6.009 4l.598 1.348L8 5.408l-1.067 1.12.425 1.47-1.356-.908-1.35.91.404-1.469L4 5.41z"/><circle cx="6" cy="6" r="6"/></g><g transform="translate(0 31)" stroke="#primary" stroke-width="2"><circle cx="6" cy="6" r="6"/><g stroke-linecap="round"><path d="M6 4v4M4 6h4"/></g></g><circle stroke="#primary" stroke-width="2" cx="10.5" cy="10.5" r="10.5"/><g stroke-linecap="round"><path d="M32.01 1.37A23.96 23.96 0 0024 0c-.999 0-1.983.061-2.95.18M.32 20.072a24.21 24.21 0 00.015 7.948M12.42 45.025A23.892 23.892 0 0024 48c13.255 0 24-10.745 24-24 0-2.811-.483-5.51-1.371-8.016" stroke="#secondary" stroke-width="2"/><path stroke="#primary" stroke-width="2" d="M8.999 7.151v5.865"/><path d="M9 3a2 2 0 110 4 2 2 0 010-4zm0 10.8a2 2 0 11-.001 4 2 2 0 01.001-4z" stroke="#primary" stroke-width="1.8"/><path d="M9.622 11.838c.138-.007.989.119 1.595-.05.607-.169 1.584-.539 1.829-1.337" stroke="#primary" stroke-width="2"/><path d="M14.8 7.202a2 2 0 110 4 2 2 0 010-4z" stroke="#primary" stroke-width="1.8"/></g></g>',
|
||||||
...rank(value, [1, 500, 1000, 3000, 5000]),
|
...rank(value, [1, 500, 1000, 3000, 5000]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.forks_rank.repositoryCount, requirement:scores.forks >= requirements.forks, type:"repositories"}),
|
leaderboard: leaderboard({user: ranks.forks_rank.repositoryCount, requirement: scores.forks >= requirements.forks, type: "repositories"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Polyglots
|
//Polyglots
|
||||||
{
|
{
|
||||||
const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node:{name}}) => name))).size
|
const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node: {name}}) => name))).size
|
||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Polyglots",
|
title: "Polyglots",
|
||||||
text:`Using ${value} different programming language${imports.s(value)}`,
|
text: `Using ${value} different programming language${imports.s(value)}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M17.135 7.988l-3.303.669a2 2 0 00-1.586 2.223l4.708 35.392a1.498 1.498 0 01-1.162 1.66 1.523 1.523 0 01-1.775-1.01L4.951 19.497a2 2 0 011.215-2.507l2.946-1.072" stroke="#secondary" stroke-linejoin="round"/><path d="M36.8 48H23a2 2 0 01-2-2V7a2 2 0 012-2h26a2 2 0 012 2v32.766" stroke="#primary"/><path d="M29 20.955l-3.399 3.399a.85.85 0 000 1.202l3.399 3.4M43.014 20.955l3.399 3.399a.85.85 0 010 1.202l-3.4 3.4" stroke="#primary" stroke-linejoin="round"/><path stroke="#primary" d="M38.526 18l-5.053 14.016"/><path d="M44 36a8 8 0 110 16 8 8 0 010-16z" stroke="#primary" stroke-linejoin="round"/><path d="M43.068 40.749l3.846 2.396a1 1 0 01-.006 1.7l-3.846 2.36a1 1 0 01-1.523-.853v-4.755a1 1 0 011.529-.848z" stroke="#primary" stroke-linejoin="round"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M17.135 7.988l-3.303.669a2 2 0 00-1.586 2.223l4.708 35.392a1.498 1.498 0 01-1.162 1.66 1.523 1.523 0 01-1.775-1.01L4.951 19.497a2 2 0 011.215-2.507l2.946-1.072" stroke="#secondary" stroke-linejoin="round"/><path d="M36.8 48H23a2 2 0 01-2-2V7a2 2 0 012-2h26a2 2 0 012 2v32.766" stroke="#primary"/><path d="M29 20.955l-3.399 3.399a.85.85 0 000 1.202l3.399 3.4M43.014 20.955l3.399 3.399a.85.85 0 010 1.202l-3.4 3.4" stroke="#primary" stroke-linejoin="round"/><path stroke="#primary" d="M38.526 18l-5.053 14.016"/><path d="M44 36a8 8 0 110 16 8 8 0 010-16z" stroke="#primary" stroke-linejoin="round"/><path d="M43.068 40.749l3.846 2.396a1 1 0 01-.006 1.7l-3.846 2.36a1 1 0 01-1.523-.853v-4.755a1 1 0 011.529-.848z" stroke="#primary" stroke-linejoin="round"/></g>',
|
||||||
...rank(value, [1, 8, 16, 32, 64]),
|
...rank(value, [1, 8, 16, 32, 64]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,12 +124,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Sponsors",
|
title: "Sponsors",
|
||||||
text:`Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`,
|
text: `Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`,
|
||||||
icon:'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M24 32c.267-1.727 1.973-3 4-3 2.08 0 3.787 1.318 4 3m-4-9a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M46.138 15c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C41.347 15 41 16.117 41 17.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm-31-5c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C10.347 10 10 11.117 10 12.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm6 32c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C16.347 42 16 43.117 16 44.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005z" fill="#secondary"/><path d="M8.003 29a3 3 0 110 6 3 3 0 010-6zM32.018 5.005a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path stroke="#secondary" stroke-width="2" d="M29.972 18.026L31.361 11M18.063 29.987l-7.004 1.401"/><path d="M22.604 11.886l.746 2.164m-9.313 9.296l-2.156-.712" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21.304 9a1 1 0 100-2 1 1 0 000 2zM8.076 22.346a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M33.267 44.17l-.722-2.146m9.38-9.206l2.147.743" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M34.544 49.031a1 1 0 100-2 1 1 0 000 2zm13.314-13.032a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M48.019 51.004a3 3 0 100-6 3 3 0 000 6zM35.194 35.33l10.812 11.019" stroke="#secondary" stroke-width="2"/></g>',
|
icon:
|
||||||
|
'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M24 32c.267-1.727 1.973-3 4-3 2.08 0 3.787 1.318 4 3m-4-9a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M46.138 15c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C41.347 15 41 16.117 41 17.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm-31-5c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C10.347 10 10 11.117 10 12.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm6 32c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C16.347 42 16 43.117 16 44.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005z" fill="#secondary"/><path d="M8.003 29a3 3 0 110 6 3 3 0 010-6zM32.018 5.005a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path stroke="#secondary" stroke-width="2" d="M29.972 18.026L31.361 11M18.063 29.987l-7.004 1.401"/><path d="M22.604 11.886l.746 2.164m-9.313 9.296l-2.156-.712" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21.304 9a1 1 0 100-2 1 1 0 000 2zM8.076 22.346a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M33.267 44.17l-.722-2.146m9.38-9.206l2.147.743" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M34.544 49.031a1 1 0 100-2 1 1 0 000 2zm13.314-13.032a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M48.019 51.004a3 3 0 100-6 3 3 0 000 6zM35.194 35.33l10.812 11.019" stroke="#secondary" stroke-width="2"/></g>',
|
||||||
...rank(value, [1, 5, 10, 20, 50]),
|
...rank(value, [1, 5, 10, 20, 50]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,27 +140,29 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Organization",
|
title: "Organization",
|
||||||
text:`Has ${value} member${imports.s(value)}`,
|
text: `Has ${value} member${imports.s(value)}`,
|
||||||
icon:'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M6 42c.45-3.415 3.34-6 7-6 1.874 0 3.752.956 5 3m-6-13a5 5 0 110 10 5 5 0 010-10zm38 16c-.452-3.415-3.34-6-7-6-1.874 0-3.752.956-5 3m6-13a5 5 0 100 10 5 5 0 000-10z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><path d="M37 51c-.92-4.01-4.6-7-9-7-4.401 0-8.083 2.995-9 7" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28.01 31.004a6.5 6.5 0 110 13 6.5 6.5 0 010-13z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M28 14.011a5 5 0 11-5 4.998 5 5 0 015-4.998z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><path d="M22 26c1.558-1.25 3.665-2 6-2 2.319 0 4.439.761 6 2" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M51 9V8c0-1.3-1.574-3-3-3h-8c-1.426 0-3 1.7-3 3v13l4-4h6c2.805-.031 4-1.826 4-4V9zM5 9V8c0-1.3 1.574-3 3-3h8c1.426 0 3 1.7 3 3v13l-4-4H9c-2.805-.031-4-1.826-4-4V9z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M43 11a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0zm-36 0a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0z" fill="#secondary"/></g>',
|
icon:
|
||||||
|
'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M6 42c.45-3.415 3.34-6 7-6 1.874 0 3.752.956 5 3m-6-13a5 5 0 110 10 5 5 0 010-10zm38 16c-.452-3.415-3.34-6-7-6-1.874 0-3.752.956-5 3m6-13a5 5 0 100 10 5 5 0 000-10z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><path d="M37 51c-.92-4.01-4.6-7-9-7-4.401 0-8.083 2.995-9 7" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28.01 31.004a6.5 6.5 0 110 13 6.5 6.5 0 010-13z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M28 14.011a5 5 0 11-5 4.998 5 5 0 015-4.998z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><path d="M22 26c1.558-1.25 3.665-2 6-2 2.319 0 4.439.761 6 2" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M51 9V8c0-1.3-1.574-3-3-3h-8c-1.426 0-3 1.7-3 3v13l4-4h6c2.805-.031 4-1.826 4-4V9zM5 9V8c0-1.3 1.574-3 3-3h8c1.426 0 3 1.7 3 3v13l-4-4H9c-2.805-.031-4-1.826-4-4V9z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M43 11a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0zm-36 0a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0z" fill="#secondary"/></g>',
|
||||||
...rank(value, [1, 100, 500, 1000, 2500]),
|
...rank(value, [1, 100, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Member
|
//Member
|
||||||
{
|
{
|
||||||
const {years:value} = computed.registered
|
const {years: value} = computed.registered
|
||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Member",
|
title: "Member",
|
||||||
text:`Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`,
|
text: `Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`,
|
||||||
icon:'<g xmlns="http://www.w3.org/2000/svg" transform="translate(5 4)" fill="none" fill-rule="evenodd"><path d="M46 44.557v1a2 2 0 01-2 2H2a2 2 0 01-2-2v-1" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M.75 40.993l.701.561a2.323 2.323 0 002.903 0l1.675-1.34a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.103.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.429 1.182a2.427 2.427 0 003.103-.008l.832-.695A2 2 0 0046 39.191v-1.634a2 2 0 00-2-2H2a2 2 0 00-2 2v1.875a2 2 0 00.75 1.561z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M42 31.609v.948m-38 0v-.992m25.04-15.008H35a2 2 0 012 2v1m-28 0v-1a2 2 0 012-2h6.007" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 8.557h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6a1 1 0 011-1z" stroke="#primary" stroke-width="2" stroke-linejoin="round"/><path d="M4.7 10.557c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zm35-8c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M23 5.557a2 2 0 002-2C25 2.452 24.433 0 22.273 0c-.463 0 .21 1.424-.502 1.979A2 2 0 0023 5.557z" stroke="#primary" stroke-width="2"/><path d="M4.78 27.982l1.346 1.076a3 3 0 003.748 0l1.252-1.002a3 3 0 013.748 0l1.282 1.026a3 3 0 003.711.03l1.4-1.085a3 3 0 013.75.061l1.102.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.281 1.025a3 3 0 003.712.029l1.358-1.053a2 2 0 00.775-1.58v-.97a1.95 1.95 0 00-1.95-1.95H5.942a1.912 1.912 0 00-1.912 1.912v.951a2 2 0 00.75 1.562z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" cx="16.5" cy="2.057" r="1"/><circle stroke="#secondary" cx="14.5" cy="12.057" r="1"/><circle stroke="#secondary" cx="31.5" cy="9.057" r="1"/></g>',
|
icon:
|
||||||
|
'<g xmlns="http://www.w3.org/2000/svg" transform="translate(5 4)" fill="none" fill-rule="evenodd"><path d="M46 44.557v1a2 2 0 01-2 2H2a2 2 0 01-2-2v-1" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M.75 40.993l.701.561a2.323 2.323 0 002.903 0l1.675-1.34a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.103.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.429 1.182a2.427 2.427 0 003.103-.008l.832-.695A2 2 0 0046 39.191v-1.634a2 2 0 00-2-2H2a2 2 0 00-2 2v1.875a2 2 0 00.75 1.561z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M42 31.609v.948m-38 0v-.992m25.04-15.008H35a2 2 0 012 2v1m-28 0v-1a2 2 0 012-2h6.007" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 8.557h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6a1 1 0 011-1z" stroke="#primary" stroke-width="2" stroke-linejoin="round"/><path d="M4.7 10.557c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zm35-8c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M23 5.557a2 2 0 002-2C25 2.452 24.433 0 22.273 0c-.463 0 .21 1.424-.502 1.979A2 2 0 0023 5.557z" stroke="#primary" stroke-width="2"/><path d="M4.78 27.982l1.346 1.076a3 3 0 003.748 0l1.252-1.002a3 3 0 013.748 0l1.282 1.026a3 3 0 003.711.03l1.4-1.085a3 3 0 013.75.061l1.102.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.281 1.025a3 3 0 003.712.029l1.358-1.053a2 2 0 00.775-1.58v-.97a1.95 1.95 0 00-1.95-1.95H5.942a1.912 1.912 0 00-1.912 1.912v.951a2 2 0 00.75 1.562z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" cx="16.5" cy="2.057" r="1"/><circle stroke="#secondary" cx="14.5" cy="12.057" r="1"/><circle stroke="#secondary" cx="31.5" cy="9.057" r="1"/></g>',
|
||||||
...rank(value, [1, 3, 5, 10, 15]),
|
...rank(value, [1, 3, 5, 10, 15]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,23 @@
|
|||||||
export default async function({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) {
|
export default async function({list, login, data, computed, imports, graphql, queries, rank, leaderboard}) {
|
||||||
//Initialization
|
//Initialization
|
||||||
const {user} = await graphql(queries.achievements({login}))
|
const {user} = await graphql(queries.achievements({login}))
|
||||||
const scores = {followers:user.followers.totalCount, created:user.repositories.totalCount, stars:user.popular.nodes?.[0]?.stargazers?.totalCount ?? 0, forks:Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))}
|
const scores = {followers: user.followers.totalCount, created: user.repositories.totalCount, stars: user.popular.nodes?.[0]?.stargazers?.totalCount ?? 0, forks: Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))}
|
||||||
const ranks = await graphql(queries.achievements.ranking(scores))
|
const ranks = await graphql(queries.achievements.ranking(scores))
|
||||||
const requirements = {stars:5, followers:3, forks:1, created:1}
|
const requirements = {stars: 5, followers: 3, forks: 1, created: 1}
|
||||||
|
|
||||||
//Developer
|
//Developer
|
||||||
{
|
{
|
||||||
const value = user.repositories.totalCount
|
const value = user.repositories.totalCount
|
||||||
const unlock = user.repositories.nodes?.shift()
|
const unlock = user.repositories.nodes?.shift()
|
||||||
list.push({
|
list.push({
|
||||||
title:"Developer",
|
title: "Developer",
|
||||||
text:`Published ${value} public repositor${imports.s(value, "y")}`,
|
text: `Published ${value} public repositor${imports.s(value, "y")}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#primary"><path d="M20 24l-3.397 3.398a.85.85 0 000 1.203L20.002 32M37.015 24l3.399 3.398a.85.85 0 010 1.203L37.014 32" stroke-linejoin="round"/><path d="M31.029 21.044L25.976 35.06"/></g><path stroke="#secondary" stroke-linejoin="round" d="M23.018 10h8.984M26 47h5M8 16h16m9 0h15.725M8 41h13"/><path d="M5.027 34.998c.673 2.157 1.726 4.396 2.81 6.02m43.38-19.095C50.7 19.921 49.866 17.796 48.79 16" stroke="#secondary"/><path stroke="#primary" stroke-linejoin="round" d="M26 41h17"/><path d="M7.183 16C5.186 19.582 4 23.619 4 28M42.608 47.02c2.647-1.87 5.642-5.448 7.295-9.18C51.52 34.191 52.071 30.323 52 28" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M7.226 16H28M13.343 47H21"/><path d="M13.337 47.01a24.364 24.364 0 006.19 3.45 24.527 24.527 0 007.217 1.505c2.145.108 4.672-.05 7.295-.738" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M36 47h6.647M12 10h6M37 10h6.858"/><path d="M43.852 10c-4.003-3.667-9.984-6.054-16.047-6-2.367.021-4.658.347-6.81 1.045" stroke="#primary"/><path stroke="#secondary" stroke-linejoin="round" d="M5.041 35h4.962M47 22h4.191"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#primary"><path d="M20 24l-3.397 3.398a.85.85 0 000 1.203L20.002 32M37.015 24l3.399 3.398a.85.85 0 010 1.203L37.014 32" stroke-linejoin="round"/><path d="M31.029 21.044L25.976 35.06"/></g><path stroke="#secondary" stroke-linejoin="round" d="M23.018 10h8.984M26 47h5M8 16h16m9 0h15.725M8 41h13"/><path d="M5.027 34.998c.673 2.157 1.726 4.396 2.81 6.02m43.38-19.095C50.7 19.921 49.866 17.796 48.79 16" stroke="#secondary"/><path stroke="#primary" stroke-linejoin="round" d="M26 41h17"/><path d="M7.183 16C5.186 19.582 4 23.619 4 28M42.608 47.02c2.647-1.87 5.642-5.448 7.295-9.18C51.52 34.191 52.071 30.323 52 28" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M7.226 16H28M13.343 47H21"/><path d="M13.337 47.01a24.364 24.364 0 006.19 3.45 24.527 24.527 0 007.217 1.505c2.145.108 4.672-.05 7.295-.738" stroke="#primary"/><path stroke="#primary" stroke-linejoin="round" d="M36 47h6.647M12 10h6M37 10h6.858"/><path d="M43.852 10c-4.003-3.667-9.984-6.054-16.047-6-2.367.021-4.658.347-6.81 1.045" stroke="#primary"/><path stroke="#secondary" stroke-linejoin="round" d="M5.041 35h4.962M47 22h4.191"/></g>',
|
||||||
...rank(value, [1, 20, 50, 100, 250]),
|
...rank(value, [1, 20, 50, 100, 250]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.created_rank.userCount, requirement:scores.created >= requirements.created, type:"users"}),
|
leaderboard: leaderboard({user: ranks.created_rank.userCount, requirement: scores.created >= requirements.created, type: "users"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,12 +27,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const value = user.forks.totalCount
|
const value = user.forks.totalCount
|
||||||
const unlock = user.forks.nodes?.shift()
|
const unlock = user.forks.nodes?.shift()
|
||||||
list.push({
|
list.push({
|
||||||
title:"Forker",
|
title: "Forker",
|
||||||
text:`Forked ${value} public repositor${imports.s(value, "y")}`,
|
text: `Forked ${value} public repositor${imports.s(value, "y")}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M37.303 21.591a5.84 5.84 0 00-1.877-1.177 6.138 6.138 0 00-4.432 0 5.822 5.822 0 00-1.879 1.177L28 22.638l-1.115-1.047c-1.086-1.018-2.559-1.59-4.094-1.59-1.536 0-3.008.572-4.094 1.59-1.086 1.02-1.696 2.4-1.696 3.84 0 1.441.61 2.823 1.696 3.841l1.115 1.046L28 38l8.189-7.682 1.115-1.046a5.422 5.422 0 001.256-1.761 5.126 5.126 0 000-4.157 5.426 5.426 0 00-1.256-1.763z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.967 42.705A18.922 18.922 0 0028 47a18.92 18.92 0 0011.076-3.56m-.032-30.902A18.914 18.914 0 0028 9c-4.09 0-7.876 1.292-10.976 3.49" stroke="#secondary" stroke-linecap="round"/><g transform="translate(7 10)" stroke="#primary"><path d="M6 0v7c0 2.21-1.343 3-3 3s-3-.79-3-3V0" stroke-linecap="round" stroke-linejoin="round"/><path stroke-linecap="round" d="M3 0v19.675"/><rect stroke-linejoin="round" x="1" y="20" width="4" height="16" rx="2"/></g><g transform="translate(43 10)" stroke="#primary"><path stroke-linecap="round" d="M2 15.968v3.674"/><path d="M4 15.642H0L.014 4.045A4.05 4.05 0 014.028 0L4 15.642z" stroke-linecap="round" stroke-linejoin="round"/><rect stroke-linejoin="round" y="19.968" width="4" height="16" rx="2"/></g><path d="M41.364 8.062A23.888 23.888 0 0028 4a23.89 23.89 0 00-11.95 3.182M4.75 22.021A24.045 24.045 0 004 28c0 1.723.182 3.404.527 5.024m10.195 14.971A23.888 23.888 0 0028 52c4.893 0 9.444-1.464 13.239-3.979m9-10.98A23.932 23.932 0 0052 28c0-2.792-.477-5.472-1.353-7.964" stroke="#secondary" stroke-linecap="round"/></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M37.303 21.591a5.84 5.84 0 00-1.877-1.177 6.138 6.138 0 00-4.432 0 5.822 5.822 0 00-1.879 1.177L28 22.638l-1.115-1.047c-1.086-1.018-2.559-1.59-4.094-1.59-1.536 0-3.008.572-4.094 1.59-1.086 1.02-1.696 2.4-1.696 3.84 0 1.441.61 2.823 1.696 3.841l1.115 1.046L28 38l8.189-7.682 1.115-1.046a5.422 5.422 0 001.256-1.761 5.126 5.126 0 000-4.157 5.426 5.426 0 00-1.256-1.763z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.967 42.705A18.922 18.922 0 0028 47a18.92 18.92 0 0011.076-3.56m-.032-30.902A18.914 18.914 0 0028 9c-4.09 0-7.876 1.292-10.976 3.49" stroke="#secondary" stroke-linecap="round"/><g transform="translate(7 10)" stroke="#primary"><path d="M6 0v7c0 2.21-1.343 3-3 3s-3-.79-3-3V0" stroke-linecap="round" stroke-linejoin="round"/><path stroke-linecap="round" d="M3 0v19.675"/><rect stroke-linejoin="round" x="1" y="20" width="4" height="16" rx="2"/></g><g transform="translate(43 10)" stroke="#primary"><path stroke-linecap="round" d="M2 15.968v3.674"/><path d="M4 15.642H0L.014 4.045A4.05 4.05 0 014.028 0L4 15.642z" stroke-linecap="round" stroke-linejoin="round"/><rect stroke-linejoin="round" y="19.968" width="4" height="16" rx="2"/></g><path d="M41.364 8.062A23.888 23.888 0 0028 4a23.89 23.89 0 00-11.95 3.182M4.75 22.021A24.045 24.045 0 004 28c0 1.723.182 3.404.527 5.024m10.195 14.971A23.888 23.888 0 0028 52c4.893 0 9.444-1.464 13.239-3.979m9-10.98A23.932 23.932 0 0052 28c0-2.792-.477-5.472-1.353-7.964" stroke="#secondary" stroke-linecap="round"/></g>',
|
||||||
...rank(value, [1, 5, 10, 20, 50]),
|
...rank(value, [1, 5, 10, 20, 50]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,12 +43,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.pullRequests.nodes?.shift()
|
const unlock = user.pullRequests.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Contributor",
|
title: "Contributor",
|
||||||
text:`Opened ${value} pull request${imports.s(value)}`,
|
text: `Opened ${value} pull request${imports.s(value)}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><path stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" d="M26.022 5.014h6M26.012 53.005h6M27.003 47.003h12M44.01 20.005h5M19.01 11.003h12"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M38.005 11.008h6M41 14.013v-6M14.007 47.003h6M17.002 50.004v-6"/><path d="M29.015 5.01l-5.003 5.992 5.003-5.992zM33.015 47.01l-5.003 5.992 5.003-5.992z" stroke="#secondary"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M8.01 19.002h6"/><path stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" d="M47.011 29h6"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M44.012 39.003h6"/><g stroke="#secondary"><path d="M5.36 29c4.353 0 6.4 4.472 6.4 8"/><path stroke-linecap="round" stroke-linejoin="round" d="M13.99 37.995h-5M10.989 29h-6"/></g><path d="M24.503 22c1.109 0 2.007.895 2.007 2 0 1.104-.898 2-2.007 2a2.004 2.004 0 01-2.008-2c0-1.105.9-2 2.008-2zM24.5 32a2 2 0 110 4 2 2 0 010-4zm9.5 0a2 2 0 110 4 2 2 0 010-4z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" d="M24.5 26.004v6.001"/><path d="M31.076 23.988l1.027-.023a1.998 1.998 0 011.932 2.01L34 31" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M31.588 26.222l-2.194-2.046 2.046-2.194"/><path d="M29.023 43c7.732 0 14-6.268 14-14s-6.268-14-14-14-14 6.268-14 14 6.268 14 14 14z" stroke="#primary"/></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><path stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" d="M26.022 5.014h6M26.012 53.005h6M27.003 47.003h12M44.01 20.005h5M19.01 11.003h12"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M38.005 11.008h6M41 14.013v-6M14.007 47.003h6M17.002 50.004v-6"/><path d="M29.015 5.01l-5.003 5.992 5.003-5.992zM33.015 47.01l-5.003 5.992 5.003-5.992z" stroke="#secondary"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M8.01 19.002h6"/><path stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" d="M47.011 29h6"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M44.012 39.003h6"/><g stroke="#secondary"><path d="M5.36 29c4.353 0 6.4 4.472 6.4 8"/><path stroke-linecap="round" stroke-linejoin="round" d="M13.99 37.995h-5M10.989 29h-6"/></g><path d="M24.503 22c1.109 0 2.007.895 2.007 2 0 1.104-.898 2-2.007 2a2.004 2.004 0 01-2.008-2c0-1.105.9-2 2.008-2zM24.5 32a2 2 0 110 4 2 2 0 010-4zm9.5 0a2 2 0 110 4 2 2 0 010-4z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" d="M24.5 26.004v6.001"/><path d="M31.076 23.988l1.027-.023a1.998 1.998 0 011.932 2.01L34 31" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M31.588 26.222l-2.194-2.046 2.046-2.194"/><path d="M29.023 43c7.732 0 14-6.268 14-14s-6.268-14-14-14-14 6.268-14 14 6.268 14 14 14z" stroke="#primary"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +59,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.projects.nodes?.shift()
|
const unlock = user.projects.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Manager",
|
title: "Manager",
|
||||||
text:`Created ${value} user project${imports.s(value)}`,
|
text: `Created ${value} user project${imports.s(value)}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M29 16V8.867C29 7.705 29.627 7 30.692 7h18.616C50.373 7 51 7.705 51 8.867v38.266C51 48.295 50.373 49 49.308 49H30.692C29.627 49 29 48.295 29 47.133V39m-4-23V9c0-1.253-.737-2-2-2H7c-1.263 0-2 .747-2 2v34c0 1.253.737 2 2 2h16c1.263 0 2-.747 2-2v-4" stroke="#secondary" stroke-linecap="round"/><path stroke="#secondary" d="M51.557 12.005h-22M5 12.005h21"/><path d="M14 33V22c0-1.246.649-2 1.73-2h28.54c1.081 0 1.73.754 1.73 2v11c0 1.246-.649 2-1.73 2H15.73c-1.081 0-1.73-.754-1.73-2z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 29v-3c0-.508.492-1 1-1h3c.508 0 1 .492 1 1v3c0 .508-.492 1-1 1h-3c-.508-.082-1-.492-1-1z" stroke="#primary"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M28.996 27.998h12M9.065 20.04a7.062 7.062 0 00-.023 1.728m.775 2.517c.264.495.584.954.954 1.369"/></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M29 16V8.867C29 7.705 29.627 7 30.692 7h18.616C50.373 7 51 7.705 51 8.867v38.266C51 48.295 50.373 49 49.308 49H30.692C29.627 49 29 48.295 29 47.133V39m-4-23V9c0-1.253-.737-2-2-2H7c-1.263 0-2 .747-2 2v34c0 1.253.737 2 2 2h16c1.263 0 2-.747 2-2v-4" stroke="#secondary" stroke-linecap="round"/><path stroke="#secondary" d="M51.557 12.005h-22M5 12.005h21"/><path d="M14 33V22c0-1.246.649-2 1.73-2h28.54c1.081 0 1.73.754 1.73 2v11c0 1.246-.649 2-1.73 2H15.73c-1.081 0-1.73-.754-1.73-2z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 29v-3c0-.508.492-1 1-1h3c.508 0 1 .492 1 1v3c0 .508-.492 1-1 1h-3c-.508-.082-1-.492-1-1z" stroke="#primary"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M28.996 27.998h12M9.065 20.04a7.062 7.062 0 00-.023 1.728m.775 2.517c.264.495.584.954.954 1.369"/></g>',
|
||||||
...rank(value, [1, 2, 3, 4, 5]),
|
...rank(value, [1, 2, 3, 4, 5]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +75,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.contributionsCollection.pullRequestReviewContributions.nodes?.shift()
|
const unlock = user.contributionsCollection.pullRequestReviewContributions.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Reviewer",
|
title: "Reviewer",
|
||||||
text:`Reviewed ${value} pull request${imports.s(value)}`,
|
text: `Reviewed ${value} pull request${imports.s(value)}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary"><path d="M26.009 34.01c.444-.004.9.141 1.228.414.473.394.766.959.76 1.54-.01.735-.333 1.413-.97 2.037.66.718.985 1.4.976 2.048-.012.828-.574 1.58-1.687 2.258.624.788.822 1.549.596 2.28-.225.733-.789 1.219-1.69 1.459.703.833.976 1.585.82 2.256-.178.763-.313 1.716-2.492 1.711" stroke-linejoin="round"/><g stroke-linejoin="round"><path d="M18.548 28.422c1.184-4.303-2.132-5.292-2.132-5.292-.873 2.296-1.438 3.825-4.231 8.108-1.285 1.97-1.926 3.957-1.877 5.796M18.391 34.011L24.993 34c2.412-.009.211-.005-6.602.012zM5.004 37.017l5.234-.014-5.234.014z"/></g><g stroke-linejoin="round"><path d="M18.548 28.422c1.184-4.303-2.132-5.292-2.132-5.292-.873 2.296-1.438 3.825-4.231 8.108-1.285 1.97-1.926 3.957-1.877 5.796M5.004 37.017l5.234-.014-5.234.014zM7 48.012h4.01c1.352 1.333 2.672 2 3.961 2.001 0 0 .485-.005 5.46-.005h3.536"/></g><path d="M18.793 27.022c-.062.933-.373 2.082-.933 3.446-.561 1.364-.433 2.547.383 3.547"/></g><path d="M45 16.156V23a2 2 0 01-2 2H31l-6 4v-4h-1.934M12 23V8a2 2 0 012-2h29a2 2 0 012 2v10" stroke="#primary" stroke-linejoin="round"/><path stroke="#primary" stroke-linejoin="round" d="M23 12.014l-3 3 3 3M34 12.014l3 3-3 3"/><path stroke="#primary" d="M30.029 10l-3.015 10.027"/><path d="M32 39h3l6 4v-4h8a2 2 0 002-2V22a2 2 0 00-2-2h.138" stroke="#secondary" stroke-linejoin="round"/><path stroke="#primary" stroke-linejoin="round" d="M33 29h12M33 34h6M43 34h2"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary"><path d="M26.009 34.01c.444-.004.9.141 1.228.414.473.394.766.959.76 1.54-.01.735-.333 1.413-.97 2.037.66.718.985 1.4.976 2.048-.012.828-.574 1.58-1.687 2.258.624.788.822 1.549.596 2.28-.225.733-.789 1.219-1.69 1.459.703.833.976 1.585.82 2.256-.178.763-.313 1.716-2.492 1.711" stroke-linejoin="round"/><g stroke-linejoin="round"><path d="M18.548 28.422c1.184-4.303-2.132-5.292-2.132-5.292-.873 2.296-1.438 3.825-4.231 8.108-1.285 1.97-1.926 3.957-1.877 5.796M18.391 34.011L24.993 34c2.412-.009.211-.005-6.602.012zM5.004 37.017l5.234-.014-5.234.014z"/></g><g stroke-linejoin="round"><path d="M18.548 28.422c1.184-4.303-2.132-5.292-2.132-5.292-.873 2.296-1.438 3.825-4.231 8.108-1.285 1.97-1.926 3.957-1.877 5.796M5.004 37.017l5.234-.014-5.234.014zM7 48.012h4.01c1.352 1.333 2.672 2 3.961 2.001 0 0 .485-.005 5.46-.005h3.536"/></g><path d="M18.793 27.022c-.062.933-.373 2.082-.933 3.446-.561 1.364-.433 2.547.383 3.547"/></g><path d="M45 16.156V23a2 2 0 01-2 2H31l-6 4v-4h-1.934M12 23V8a2 2 0 012-2h29a2 2 0 012 2v10" stroke="#primary" stroke-linejoin="round"/><path stroke="#primary" stroke-linejoin="round" d="M23 12.014l-3 3 3 3M34 12.014l3 3-3 3"/><path stroke="#primary" d="M30.029 10l-3.015 10.027"/><path d="M32 39h3l6 4v-4h8a2 2 0 002-2V22a2 2 0 00-2-2h.138" stroke="#secondary" stroke-linejoin="round"/><path stroke="#primary" stroke-linejoin="round" d="M33 29h12M33 34h6M43 34h2"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +91,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.packages.nodes?.shift()
|
const unlock = user.packages.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Packager",
|
title: "Packager",
|
||||||
text:`Created ${value} package${imports.s(value)}`,
|
text: `Created ${value} package${imports.s(value)}`,
|
||||||
icon:'<g fill="none"><path fill="#secondary" d="M28.53 27.64l-11.2 6.49V21.15l11.23-6.48z"/><path d="M40.4 34.84c-.17 0-.34-.04-.5-.13l-11.24-6.44a.99.99 0 01-.37-1.36.99.99 0 011.36-.37l11.24 6.44c.48.27.65.89.37 1.36-.17.32-.51.5-.86.5z" fill="#primary"/><path d="M29.16 28.4c-.56 0-1-.45-1-1.01l.08-12.47c0-.55.49-1 1.01-.99.55 0 1 .45.99 1.01l-.08 12.47c0 .55-.45.99-1 .99z" fill="#primary"/><path d="M18.25 34.65a.996.996 0 01-.5-1.86l10.91-6.25a.997.997 0 11.99 1.73l-10.91 6.25c-.15.09-.32.13-.49.13z" fill="#primary"/><path d="M29.19 41.37c-.17 0-.35-.04-.5-.13l-11.23-6.49c-.31-.18-.5-.51-.5-.87V20.91c0-.36.19-.69.5-.87l11.23-6.49c.31-.18.69-.18 1 0l11.23 6.49c.31.18.5.51.5.87v12.97c0 .36-.19.69-.5.87l-11.23 6.49c-.15.08-.32.13-.5.13zm-10.23-8.06l10.23 5.91 10.23-5.91V21.49l-10.23-5.91-10.23 5.91v11.82zM40.5 11.02c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.19 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm23.37 43.8c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.35c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.06 4.11c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zM6.18 30.72C4.43 30.72 3 29.29 3 27.54c0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm45.64 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18z" fill="#primary"/><path d="M29.1 10.21c-.55 0-1-.45-1-1V3.52c0-.55.45-1 1-1s1 .45 1 1v5.69c0 .56-.45 1-1 1zM7.44 20.95c-.73 0-1.32-.59-1.32-1.32v-5.38l4.66-2.69c.63-.37 1.44-.15 1.8.48.36.63.15 1.44-.48 1.8l-3.34 1.93v3.86c0 .73-.59 1.32-1.32 1.32zm4 22.68c-.22 0-.45-.06-.66-.18l-4.66-2.69v-5.38c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v3.86l3.34 1.93c.63.36.85 1.17.48 1.8-.24.42-.68.66-1.14.66zm17.64 10.39l-4.66-2.69c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l3.34 1.93 3.34-1.93a1.32 1.32 0 011.8.48c.36.63.15 1.44-.48 1.8l-4.66 2.69zm17.64-10.39a1.32 1.32 0 01-.66-2.46l3.34-1.93v-3.86c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v5.38l-4.66 2.69c-.21.12-.44.18-.66.18zm4-22.68c-.73 0-1.32-.59-1.32-1.32v-3.86l-3.34-1.93c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l4.66 2.69v5.38c0 .73-.59 1.32-1.32 1.32z" fill="#secondary"/><path d="M33.08 6.15c-.22 0-.45-.06-.66-.18l-3.34-1.93-3.34 1.93c-.63.36-1.44.15-1.8-.48a1.32 1.32 0 01.48-1.8L29.08 1l4.66 2.69c.63.36.85 1.17.48 1.8a1.3 1.3 0 01-1.14.66zm-3.99 47.3c-.55 0-1-.45-1-1v-7.13c0-.55.45-1 1-1s1 .45 1 1v7.13c0 .55-.44 1-1 1zM13.86 19.71c-.17 0-.34-.04-.5-.13L7.2 16a1 1 0 011-1.73l6.17 3.58c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zm36.63 21.23c-.17 0-.34-.04-.5-.13l-6.17-3.57a.998.998 0 01-.36-1.37c.28-.48.89-.64 1.37-.36L51 39.08c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zM44.06 19.8c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.16.1-.33.14-.5.14zM7.43 41.03c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.15.09-.33.14-.5.14z" fill="#secondary"/></g>',
|
icon:
|
||||||
|
'<g fill="none"><path fill="#secondary" d="M28.53 27.64l-11.2 6.49V21.15l11.23-6.48z"/><path d="M40.4 34.84c-.17 0-.34-.04-.5-.13l-11.24-6.44a.99.99 0 01-.37-1.36.99.99 0 011.36-.37l11.24 6.44c.48.27.65.89.37 1.36-.17.32-.51.5-.86.5z" fill="#primary"/><path d="M29.16 28.4c-.56 0-1-.45-1-1.01l.08-12.47c0-.55.49-1 1.01-.99.55 0 1 .45.99 1.01l-.08 12.47c0 .55-.45.99-1 .99z" fill="#primary"/><path d="M18.25 34.65a.996.996 0 01-.5-1.86l10.91-6.25a.997.997 0 11.99 1.73l-10.91 6.25c-.15.09-.32.13-.49.13z" fill="#primary"/><path d="M29.19 41.37c-.17 0-.35-.04-.5-.13l-11.23-6.49c-.31-.18-.5-.51-.5-.87V20.91c0-.36.19-.69.5-.87l11.23-6.49c.31-.18.69-.18 1 0l11.23 6.49c.31.18.5.51.5.87v12.97c0 .36-.19.69-.5.87l-11.23 6.49c-.15.08-.32.13-.5.13zm-10.23-8.06l10.23 5.91 10.23-5.91V21.49l-10.23-5.91-10.23 5.91v11.82zM40.5 11.02c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.19 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm23.37 43.8c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.42 3.18-3.18 3.18zm0-4.35c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm-23.06 4.11c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zM6.18 30.72C4.43 30.72 3 29.29 3 27.54c0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18zm45.64 4.36c-1.75 0-3.18-1.43-3.18-3.18 0-1.75 1.43-3.18 3.18-3.18 1.75 0 3.18 1.43 3.18 3.18 0 1.75-1.43 3.18-3.18 3.18zm0-4.36c-.65 0-1.18.53-1.18 1.18 0 .65.53 1.18 1.18 1.18.65 0 1.18-.53 1.18-1.18 0-.65-.53-1.18-1.18-1.18z" fill="#primary"/><path d="M29.1 10.21c-.55 0-1-.45-1-1V3.52c0-.55.45-1 1-1s1 .45 1 1v5.69c0 .56-.45 1-1 1zM7.44 20.95c-.73 0-1.32-.59-1.32-1.32v-5.38l4.66-2.69c.63-.37 1.44-.15 1.8.48.36.63.15 1.44-.48 1.8l-3.34 1.93v3.86c0 .73-.59 1.32-1.32 1.32zm4 22.68c-.22 0-.45-.06-.66-.18l-4.66-2.69v-5.38c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v3.86l3.34 1.93c.63.36.85 1.17.48 1.8-.24.42-.68.66-1.14.66zm17.64 10.39l-4.66-2.69c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l3.34 1.93 3.34-1.93a1.32 1.32 0 011.8.48c.36.63.15 1.44-.48 1.8l-4.66 2.69zm17.64-10.39a1.32 1.32 0 01-.66-2.46l3.34-1.93v-3.86c0-.73.59-1.32 1.32-1.32.73 0 1.32.59 1.32 1.32v5.38l-4.66 2.69c-.21.12-.44.18-.66.18zm4-22.68c-.73 0-1.32-.59-1.32-1.32v-3.86l-3.34-1.93c-.63-.36-.85-1.17-.48-1.8.36-.63 1.17-.85 1.8-.48l4.66 2.69v5.38c0 .73-.59 1.32-1.32 1.32z" fill="#secondary"/><path d="M33.08 6.15c-.22 0-.45-.06-.66-.18l-3.34-1.93-3.34 1.93c-.63.36-1.44.15-1.8-.48a1.32 1.32 0 01.48-1.8L29.08 1l4.66 2.69c.63.36.85 1.17.48 1.8a1.3 1.3 0 01-1.14.66zm-3.99 47.3c-.55 0-1-.45-1-1v-7.13c0-.55.45-1 1-1s1 .45 1 1v7.13c0 .55-.44 1-1 1zM13.86 19.71c-.17 0-.34-.04-.5-.13L7.2 16a1 1 0 011-1.73l6.17 3.58c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zm36.63 21.23c-.17 0-.34-.04-.5-.13l-6.17-3.57a.998.998 0 01-.36-1.37c.28-.48.89-.64 1.37-.36L51 39.08c.48.28.64.89.36 1.37-.19.31-.52.49-.87.49zM44.06 19.8c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.16.1-.33.14-.5.14zM7.43 41.03c-.35 0-.68-.18-.87-.5-.28-.48-.11-1.09.36-1.37l6.17-3.57c.48-.28 1.09-.11 1.37.36.28.48.11 1.09-.36 1.37l-6.17 3.57c-.15.09-.33.14-.5.14z" fill="#secondary"/></g>',
|
||||||
...rank(value, [1, 5, 10, 20, 30]),
|
...rank(value, [1, 5, 10, 20, 30]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,12 +107,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.gists.nodes?.shift()
|
const unlock = user.gists.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Gister",
|
title: "Gister",
|
||||||
text:`Published ${value} gist${imports.s(value)}`,
|
text: `Published ${value} gist${imports.s(value)}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M20 48.875v-12.75c0-1.33.773-2.131 2.385-2.125h26.23c1.612-.006 2.385.795 2.385 2.125v12.75C51 50.198 50.227 51 48.615 51h-26.23C20.773 51 20 50.198 20 48.875zM37 40.505h9M37 44.492h6" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" d="M14 30h-4M16 35h-3M47 10H5M42 15H24M19 15h-9M16 25h-3M42 20h-2M42 20h-2M42 25h-2M16 20h-3"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M31.974 25H24"/><path d="M22 20h12a2 2 0 012 2v6a2 2 0 01-2 2H22a2 2 0 01-2-2v-6a2 2 0 012-2z" stroke="#primary"/><path d="M5 33V7a2 2 0 012-2h38a2 2 0 012 2v23" stroke="#secondary" stroke-linecap="round"/><path d="M5 30v8c0 1.105.892 2 1.993 2H16" stroke="#secondary" stroke-linecap="round"/><g stroke="#primary" stroke-linecap="round"><path d="M26.432 37.933v7.07M26.432 37.933v9.07M24.432 40.433h7.07M24.432 40.433h8.07M24.432 44.433h7.07M24.432 44.433h8.07M30.432 37.933v9.07"/></g></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><path d="M20 48.875v-12.75c0-1.33.773-2.131 2.385-2.125h26.23c1.612-.006 2.385.795 2.385 2.125v12.75C51 50.198 50.227 51 48.615 51h-26.23C20.773 51 20 50.198 20 48.875zM37 40.505h9M37 44.492h6" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" d="M14 30h-4M16 35h-3M47 10H5M42 15H24M19 15h-9M16 25h-3M42 20h-2M42 20h-2M42 25h-2M16 20h-3"/><path stroke="#primary" stroke-linecap="round" stroke-linejoin="round" d="M31.974 25H24"/><path d="M22 20h12a2 2 0 012 2v6a2 2 0 01-2 2H22a2 2 0 01-2-2v-6a2 2 0 012-2z" stroke="#primary"/><path d="M5 33V7a2 2 0 012-2h38a2 2 0 012 2v23" stroke="#secondary" stroke-linecap="round"/><path d="M5 30v8c0 1.105.892 2 1.993 2H16" stroke="#secondary" stroke-linecap="round"/><g stroke="#primary" stroke-linecap="round"><path d="M26.432 37.933v7.07M26.432 37.933v9.07M24.432 40.433h7.07M24.432 40.433h8.07M24.432 44.433h7.07M24.432 44.433h8.07M30.432 37.933v9.07"/></g></g>',
|
||||||
...rank(value, [1, 20, 50, 100, 250]),
|
...rank(value, [1, 20, 50, 100, 250]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +123,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.organizations.nodes?.shift()
|
const unlock = user.organizations.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Worker",
|
title: "Worker",
|
||||||
text:`Joined ${value} organization${imports.s(value)}`,
|
text: `Joined ${value} organization${imports.s(value)}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary" stroke-linejoin="round"><path d="M30 51H16.543v-2.998h-4v2.976l-5.537.016a2 2 0 01-2.006-2v-8.032a2 2 0 01.75-1.562l9.261-7.406 5.984 5.143m29.992 3.864v10h-6v-3h-5v3h-6m-.987-33c.133-1.116.793-2.106 1.978-2.968.44-.32 5.776-3.664 16.01-10.032v36"/><path d="M19 34.994v-8.982m16 0V49a2 2 0 01-2 2h-8.987l.011-6.957"/></g><path stroke="#secondary" d="M40 38h5M40 34h5"/><path stroke="#primary" d="M25 30h5M25 34h5M25 26h5"/><path d="M35.012 22.003H9.855a4.843 4.843 0 010-9.686h1.479c1.473-4.268 4.277-6.674 8.41-7.219 6.493-.856 9.767 4.27 10.396 5.9.734-.83 2.137-2.208 4.194-1.964a4.394 4.394 0 011.685.533" stroke="#primary" stroke-linejoin="round"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary" stroke-linejoin="round"><path d="M30 51H16.543v-2.998h-4v2.976l-5.537.016a2 2 0 01-2.006-2v-8.032a2 2 0 01.75-1.562l9.261-7.406 5.984 5.143m29.992 3.864v10h-6v-3h-5v3h-6m-.987-33c.133-1.116.793-2.106 1.978-2.968.44-.32 5.776-3.664 16.01-10.032v36"/><path d="M19 34.994v-8.982m16 0V49a2 2 0 01-2 2h-8.987l.011-6.957"/></g><path stroke="#secondary" d="M40 38h5M40 34h5"/><path stroke="#primary" d="M25 30h5M25 34h5M25 26h5"/><path d="M35.012 22.003H9.855a4.843 4.843 0 010-9.686h1.479c1.473-4.268 4.277-6.674 8.41-7.219 6.493-.856 9.767 4.27 10.396 5.9.734-.83 2.137-2.208 4.194-1.964a4.394 4.394 0 011.685.533" stroke="#primary" stroke-linejoin="round"/></g>',
|
||||||
...rank(value, [1, 2, 4, 8, 10]),
|
...rank(value, [1, 2, 4, 8, 10]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,12 +139,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.starredRepositories.nodes?.shift()
|
const unlock = user.starredRepositories.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Stargazer",
|
title: "Stargazer",
|
||||||
text:`Starred ${value} repositor${imports.s(value, "y")}`,
|
text: `Starred ${value} repositor${imports.s(value, "y")}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" fill-rule="evenodd"><path stroke="#primary" d="M28.017 5v3M36.006 7.013l-1.987 2.024M20.021 7.011l1.988 2.011M28.806 30.23c-2.206-3.88-5.25-2.234-5.25-2.234 1.007 2.24 1.688 3.72 2.742 8.724.957 4.551 3.785 7.409 7.687 7.293l5.028 6.003M29.03 34.057L29 20.007m4.012 9.004V17.005m4.006 11.99l-.003-9.353"/><path d="M18.993 50.038l4.045-5.993s1.03-.262 1.954-.984m-6.983.96c-4.474-.016-6.986-5.558-6.986-9.979 0-1.764-.439-4.997-1.997-8.004 0 0 3.268-1.24 5.747 3.6.904 1.768.458 5.267.642 5.388.185.121 1.336.554 2.637 2.01m4.955-18.92a976.92 976.92 0 010 5.91m-7.995-4.986l-.003 10.97M10.031 48.021l2.369-3.003" stroke="#secondary"/><path d="M45.996 47.026l-1.99-2.497-1.993-2.5s2.995-1.485 2.995-6.46V24.033" stroke="#primary"/><path d="M41 29v-6a2 2 0 114 0v2m-8-4v-4a2 2 0 114 0v7m-8-7v-2a2 2 0 114 0v2m-8 4v-2a2 2 0 114 0v2" stroke="#primary"/><path d="M23 20v-2a2 2 0 013.043-1.707M19 19v-4a2 2 0 114 0v3m-8 3v-2a2 2 0 114 0v10" stroke="#secondary"/><path d="M6.7 12c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.316-1.121-.572-1.372-1.71-1.678 1.135-.314 1.389-.567 1.7-1.69zm42 0c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zM28.021 47.627c.317 1.122.573 1.372 1.71 1.678-1.135.314-1.389.566-1.699 1.69-.318-1.121-.573-1.372-1.71-1.679 1.134-.313 1.389-.566 1.699-1.689z" stroke="#primary"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" fill-rule="evenodd"><path stroke="#primary" d="M28.017 5v3M36.006 7.013l-1.987 2.024M20.021 7.011l1.988 2.011M28.806 30.23c-2.206-3.88-5.25-2.234-5.25-2.234 1.007 2.24 1.688 3.72 2.742 8.724.957 4.551 3.785 7.409 7.687 7.293l5.028 6.003M29.03 34.057L29 20.007m4.012 9.004V17.005m4.006 11.99l-.003-9.353"/><path d="M18.993 50.038l4.045-5.993s1.03-.262 1.954-.984m-6.983.96c-4.474-.016-6.986-5.558-6.986-9.979 0-1.764-.439-4.997-1.997-8.004 0 0 3.268-1.24 5.747 3.6.904 1.768.458 5.267.642 5.388.185.121 1.336.554 2.637 2.01m4.955-18.92a976.92 976.92 0 010 5.91m-7.995-4.986l-.003 10.97M10.031 48.021l2.369-3.003" stroke="#secondary"/><path d="M45.996 47.026l-1.99-2.497-1.993-2.5s2.995-1.485 2.995-6.46V24.033" stroke="#primary"/><path d="M41 29v-6a2 2 0 114 0v2m-8-4v-4a2 2 0 114 0v7m-8-7v-2a2 2 0 114 0v2m-8 4v-2a2 2 0 114 0v2" stroke="#primary"/><path d="M23 20v-2a2 2 0 013.043-1.707M19 19v-4a2 2 0 114 0v3m-8 3v-2a2 2 0 114 0v10" stroke="#secondary"/><path d="M6.7 12c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.316-1.121-.572-1.372-1.71-1.678 1.135-.314 1.389-.567 1.7-1.69zm42 0c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zM28.021 47.627c.317 1.122.573 1.372 1.71 1.678-1.135.314-1.389.566-1.699 1.69-.318-1.121-.573-1.372-1.71-1.679 1.134-.313 1.389-.566 1.699-1.689z" stroke="#primary"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +155,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.following.nodes?.shift()
|
const unlock = user.following.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Follower",
|
title: "Follower",
|
||||||
text:`Following ${value} user${imports.s(value)}`,
|
text: `Following ${value} user${imports.s(value)}`,
|
||||||
icon:'<g fill="none" fill-rule="evenodd"><path d="M35 31a7 7 0 1114 0 7 7 0 01-14 0zm12-13a3 3 0 116 0 3 3 0 01-6 0zM33 49a3 3 0 116 0 3 3 0 01-6 0zM4 15a3 3 0 116 0 3 3 0 01-6 0zm37-8.5a2.5 2.5 0 115 0 2.5 2.5 0 01-5 0zM10 14l4.029-.576M19.008 26.016L21 19M29.019 34.001l5.967-1.948M36.997 46.003l2.977-8.02M46.05 24.031L48 21M28.787 18.012l7.248 8.009" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M43.62 29.004c-1.157 0-1.437.676-1.62 1.173-.19-.498-.494-1.167-1.629-1.167-.909 0-1.355.777-1.371 1.632-.022 1.145 1.309 2.365 3 3.358 1.669-.983 3-2.23 3-3.358 0-.89-.54-1.638-1.38-1.638z" fill="#primary"/><path stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M48.043 15.003L45 9"/><path d="M21 12a3 3 0 116 0 3 3 0 01-6 0zM27 12h3M18 12h3M21 43c-.267-1.727-1.973-3-4-3-2.08 0-3.787 1.318-4 3m4-9a3 3 0 100 6 3 3 0 000-6z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 30a9 9 0 110 18 9 9 0 110-18z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g>',
|
icon:
|
||||||
|
'<g fill="none" fill-rule="evenodd"><path d="M35 31a7 7 0 1114 0 7 7 0 01-14 0zm12-13a3 3 0 116 0 3 3 0 01-6 0zM33 49a3 3 0 116 0 3 3 0 01-6 0zM4 15a3 3 0 116 0 3 3 0 01-6 0zm37-8.5a2.5 2.5 0 115 0 2.5 2.5 0 01-5 0zM10 14l4.029-.576M19.008 26.016L21 19M29.019 34.001l5.967-1.948M36.997 46.003l2.977-8.02M46.05 24.031L48 21M28.787 18.012l7.248 8.009" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M43.62 29.004c-1.157 0-1.437.676-1.62 1.173-.19-.498-.494-1.167-1.629-1.167-.909 0-1.355.777-1.371 1.632-.022 1.145 1.309 2.365 3 3.358 1.669-.983 3-2.23 3-3.358 0-.89-.54-1.638-1.38-1.638z" fill="#primary"/><path stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M48.043 15.003L45 9"/><path d="M21 12a3 3 0 116 0 3 3 0 01-6 0zM27 12h3M18 12h3M21 43c-.267-1.727-1.973-3-4-3-2.08 0-3.787 1.318-4 3m4-9a3 3 0 100 6 3 3 0 000-6z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M17 30a9 9 0 110 18 9 9 0 110-18z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,13 +171,14 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = user.followers.nodes?.shift()
|
const unlock = user.followers.nodes?.shift()
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Influencer",
|
title: "Influencer",
|
||||||
text:`Followed by ${value} user${imports.s(value)}`,
|
text: `Followed by ${value} user${imports.s(value)}`,
|
||||||
icon:'<g transform="translate(4 4)" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M33.432 1.924A23.922 23.922 0 0024 0c-3.945 0-7.668.952-10.95 2.638m-9.86 9.398A23.89 23.89 0 000 24a23.9 23.9 0 002.274 10.21m3.45 5.347a23.992 23.992 0 0012.929 7.845m13.048-.664c4.43-1.5 8.28-4.258 11.123-7.848m3.16-5.245A23.918 23.918 0 0048 24c0-1.87-.214-3.691-.619-5.439M40.416 6.493a24.139 24.139 0 00-1.574-1.355" stroke="#secondary" stroke-linecap="round"/><path stroke="#secondary" d="M4.582 33.859l1.613-7.946"/><circle stroke="#secondary" cx="6.832" cy="23" r="3"/><path stroke="#primary" d="M17.444 39.854l4.75 3.275"/><path stroke="#secondary" stroke-linecap="round" d="M7.647 14.952l-.433 4.527"/><circle stroke="#primary" cx="15" cy="38" r="3"/><path stroke="#primary" d="M22.216 9.516l.455 4.342"/><path stroke="#secondary" stroke-linecap="round" d="M34.272 6.952l-2.828 5.25"/><path stroke="#primary" stroke-linecap="square" d="M11.873 7.235l6.424-.736"/><path stroke="#secondary" stroke-linecap="round" d="M28.811 5.445l3.718-.671"/><path stroke="#primary" d="M42.392 22.006l.456-5.763M34.349 24.426l4.374.447"/><path d="M20 28c.267-1.727 1.973-3 4-3 2.08 0 3.787 1.318 4 3m-4-9a3 3 0 110 6 3 3 0 010-6z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M24 14c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" cx="35.832" cy="4" r="3"/><circle stroke="#secondary" cx="44" cy="36" r="3"/><circle stroke="#secondary" cx="34.832" cy="37" r="3"/><circle stroke="#primary" cx="21.654" cy="6.437" r="3"/><path d="M25.083 48.102a3 3 0 100-6 3 3 0 000 6z" stroke="#primary"/><path d="M8.832 5a3 3 0 110 6 3 3 0 010-6z" stroke="#primary" stroke-linecap="round"/><circle stroke="#secondary" cx="4" cy="37" r="3"/><path d="M42.832 10a3 3 0 110 6 3 3 0 010-6z" stroke="#primary" stroke-linecap="round"/><path stroke="#secondary" stroke-linecap="round" d="M32.313 38.851l-1.786 1.661"/><circle stroke="#primary" cx="42" cy="25" r="3"/><path stroke="#primary" stroke-linecap="square" d="M18.228 32.388l-1.562 2.66"/><path stroke="#secondary" d="M37.831 36.739l2.951-.112"/></g>',
|
icon:
|
||||||
|
'<g transform="translate(4 4)" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M33.432 1.924A23.922 23.922 0 0024 0c-3.945 0-7.668.952-10.95 2.638m-9.86 9.398A23.89 23.89 0 000 24a23.9 23.9 0 002.274 10.21m3.45 5.347a23.992 23.992 0 0012.929 7.845m13.048-.664c4.43-1.5 8.28-4.258 11.123-7.848m3.16-5.245A23.918 23.918 0 0048 24c0-1.87-.214-3.691-.619-5.439M40.416 6.493a24.139 24.139 0 00-1.574-1.355" stroke="#secondary" stroke-linecap="round"/><path stroke="#secondary" d="M4.582 33.859l1.613-7.946"/><circle stroke="#secondary" cx="6.832" cy="23" r="3"/><path stroke="#primary" d="M17.444 39.854l4.75 3.275"/><path stroke="#secondary" stroke-linecap="round" d="M7.647 14.952l-.433 4.527"/><circle stroke="#primary" cx="15" cy="38" r="3"/><path stroke="#primary" d="M22.216 9.516l.455 4.342"/><path stroke="#secondary" stroke-linecap="round" d="M34.272 6.952l-2.828 5.25"/><path stroke="#primary" stroke-linecap="square" d="M11.873 7.235l6.424-.736"/><path stroke="#secondary" stroke-linecap="round" d="M28.811 5.445l3.718-.671"/><path stroke="#primary" d="M42.392 22.006l.456-5.763M34.349 24.426l4.374.447"/><path d="M20 28c.267-1.727 1.973-3 4-3 2.08 0 3.787 1.318 4 3m-4-9a3 3 0 110 6 3 3 0 010-6z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path d="M24 14c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" cx="35.832" cy="4" r="3"/><circle stroke="#secondary" cx="44" cy="36" r="3"/><circle stroke="#secondary" cx="34.832" cy="37" r="3"/><circle stroke="#primary" cx="21.654" cy="6.437" r="3"/><path d="M25.083 48.102a3 3 0 100-6 3 3 0 000 6z" stroke="#primary"/><path d="M8.832 5a3 3 0 110 6 3 3 0 010-6z" stroke="#primary" stroke-linecap="round"/><circle stroke="#secondary" cx="4" cy="37" r="3"/><path d="M42.832 10a3 3 0 110 6 3 3 0 010-6z" stroke="#primary" stroke-linecap="round"/><path stroke="#secondary" stroke-linecap="round" d="M32.313 38.851l-1.786 1.661"/><circle stroke="#primary" cx="42" cy="25" r="3"/><path stroke="#primary" stroke-linecap="square" d="M18.228 32.388l-1.562 2.66"/><path stroke="#secondary" d="M37.831 36.739l2.951-.112"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.user_rank.userCount, requirement:scores.followers >= requirements.followers, type:"users"}),
|
leaderboard: leaderboard({user: ranks.user_rank.userCount, requirement: scores.followers >= requirements.followers, type: "users"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +188,14 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Maintainer",
|
title: "Maintainer",
|
||||||
text:`Maintaining a repository with ${value} star${imports.s(value)}`,
|
text: `Maintaining a repository with ${value} star${imports.s(value)}`,
|
||||||
icon:'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M39 15h.96l4.038 3-.02-3H45a2 2 0 002-2V3a2 2 0 00-2-2H31a2 2 0 00-2 2v4.035" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M36 5.014l-3 3 3 3M40 5.014l3 3-3 3"/><path d="M6 37a1 1 0 110 2 1 1 0 010-2m7 0a1 1 0 110 2 1 1 0 010-2m-2.448 1a1 1 0 11-2 0 1 1 0 012 0z" fill="#primary"/><path d="M1.724 15.05A23.934 23.934 0 000 24c0 .686.029 1.366.085 2.037m19.92 21.632c1.3.218 2.634.331 3.995.331a23.92 23.92 0 009.036-1.76m13.207-13.21A23.932 23.932 0 0048 24c0-1.363-.114-2.7-.332-4M25.064.022a23.932 23.932 0 00-10.073 1.725" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M19 42.062V43a2 2 0 01-2 2H9.04l-4.038 3 .02-3H3a2 2 0 01-2-2V33a2 2 0 012-2h4.045" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 0a6 6 0 110 12A6 6 0 016 0z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" d="M6 3v6M3 6h6"/><path d="M42 36a6 6 0 110 12 6 6 0 010-12z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M44.338 40.663l-3.336 3.331-1.692-1.686M31 31c-.716-2.865-3.578-5-7-5-3.423 0-6.287 2.14-7 5"/><path d="M24 16a5 5 0 110 10 5 5 0 010-10z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><circle stroke="#primary" stroke-width="2" cx="24" cy="24" r="14"/></g>',
|
icon:
|
||||||
|
'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M39 15h.96l4.038 3-.02-3H45a2 2 0 002-2V3a2 2 0 00-2-2H31a2 2 0 00-2 2v4.035" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M36 5.014l-3 3 3 3M40 5.014l3 3-3 3"/><path d="M6 37a1 1 0 110 2 1 1 0 010-2m7 0a1 1 0 110 2 1 1 0 010-2m-2.448 1a1 1 0 11-2 0 1 1 0 012 0z" fill="#primary"/><path d="M1.724 15.05A23.934 23.934 0 000 24c0 .686.029 1.366.085 2.037m19.92 21.632c1.3.218 2.634.331 3.995.331a23.92 23.92 0 009.036-1.76m13.207-13.21A23.932 23.932 0 0048 24c0-1.363-.114-2.7-.332-4M25.064.022a23.932 23.932 0 00-10.073 1.725" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M19 42.062V43a2 2 0 01-2 2H9.04l-4.038 3 .02-3H3a2 2 0 01-2-2V33a2 2 0 012-2h4.045" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 0a6 6 0 110 12A6 6 0 016 0z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" d="M6 3v6M3 6h6"/><path d="M42 36a6 6 0 110 12 6 6 0 010-12z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M44.338 40.663l-3.336 3.331-1.692-1.686M31 31c-.716-2.865-3.578-5-7-5-3.423 0-6.287 2.14-7 5"/><path d="M24 16a5 5 0 110 10 5 5 0 010-10z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><circle stroke="#primary" stroke-width="2" cx="24" cy="24" r="14"/></g>',
|
||||||
...rank(value, [1, 1000, 5000, 10000, 25000]),
|
...rank(value, [1, 1000, 5000, 10000, 25000]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.repo_rank.repositoryCount, requirement:scores.stars >= requirements.stars, type:"repositories"}),
|
leaderboard: leaderboard({user: ranks.repo_rank.repositoryCount, requirement: scores.stars >= requirements.stars, type: "repositories"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,43 +204,46 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))
|
const value = Math.max(0, ...data.user.repositories.nodes.map(({forkCount}) => forkCount))
|
||||||
const unlock = null
|
const unlock = null
|
||||||
list.push({
|
list.push({
|
||||||
title:"Inspirer",
|
title: "Inspirer",
|
||||||
text:`Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`,
|
text: `Maintaining or created a repository which has been forked ${value} time${imports.s(value)}`,
|
||||||
icon:'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M20.065 47.122c.44-.525.58-1.448.58-1.889 0-2.204-1.483-3.967-3.633-4.187.447-1.537.58-2.64.397-3.31-.25-.92-.745-1.646-1.409-2.235m-5.97-7.157c.371-.254.911-.748 1.62-1.48a8.662 8.662 0 001.432-2.366M47 22h-7c-1.538 0-2.749-.357-4-1h-5c-1.789.001-3-1.3-3-2.955 0-1.656 1.211-3.04 3-3.045h2c.027-1.129.513-2.17 1-3m3.082 32.004C34.545 43.028 34.02 40.569 34 39v-1h-1c-2.603-.318-5-2.913-5-5.997S30.397 26 33 26h9c2.384 0 4.326 1.024 5.27 3" stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><g transform="translate(36)" stroke="#primary" stroke-width="2"><path fill="#primary" stroke-linecap="round" stroke-linejoin="round" d="M5.395 5.352L6.009 4l.598 1.348L8 5.408l-1.067 1.12.425 1.47-1.356-.908-1.35.91.404-1.469L4 5.41z"/><circle cx="6" cy="6" r="6"/></g><g transform="translate(0 31)" stroke="#primary" stroke-width="2"><circle cx="6" cy="6" r="6"/><g stroke-linecap="round"><path d="M6 4v4M4 6h4"/></g></g><circle stroke="#primary" stroke-width="2" cx="10.5" cy="10.5" r="10.5"/><g stroke-linecap="round"><path d="M32.01 1.37A23.96 23.96 0 0024 0c-.999 0-1.983.061-2.95.18M.32 20.072a24.21 24.21 0 00.015 7.948M12.42 45.025A23.892 23.892 0 0024 48c13.255 0 24-10.745 24-24 0-2.811-.483-5.51-1.371-8.016" stroke="#secondary" stroke-width="2"/><path stroke="#primary" stroke-width="2" d="M8.999 7.151v5.865"/><path d="M9 3a2 2 0 110 4 2 2 0 010-4zm0 10.8a2 2 0 11-.001 4 2 2 0 01.001-4z" stroke="#primary" stroke-width="1.8"/><path d="M9.622 11.838c.138-.007.989.119 1.595-.05.607-.169 1.584-.539 1.829-1.337" stroke="#primary" stroke-width="2"/><path d="M14.8 7.202a2 2 0 110 4 2 2 0 010-4z" stroke="#primary" stroke-width="1.8"/></g></g>',
|
icon:
|
||||||
|
'<g transform="translate(4 4)" fill="none" fill-rule="evenodd"><path d="M20.065 47.122c.44-.525.58-1.448.58-1.889 0-2.204-1.483-3.967-3.633-4.187.447-1.537.58-2.64.397-3.31-.25-.92-.745-1.646-1.409-2.235m-5.97-7.157c.371-.254.911-.748 1.62-1.48a8.662 8.662 0 001.432-2.366M47 22h-7c-1.538 0-2.749-.357-4-1h-5c-1.789.001-3-1.3-3-2.955 0-1.656 1.211-3.04 3-3.045h2c.027-1.129.513-2.17 1-3m3.082 32.004C34.545 43.028 34.02 40.569 34 39v-1h-1c-2.603-.318-5-2.913-5-5.997S30.397 26 33 26h9c2.384 0 4.326 1.024 5.27 3" stroke="#secondary" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><g transform="translate(36)" stroke="#primary" stroke-width="2"><path fill="#primary" stroke-linecap="round" stroke-linejoin="round" d="M5.395 5.352L6.009 4l.598 1.348L8 5.408l-1.067 1.12.425 1.47-1.356-.908-1.35.91.404-1.469L4 5.41z"/><circle cx="6" cy="6" r="6"/></g><g transform="translate(0 31)" stroke="#primary" stroke-width="2"><circle cx="6" cy="6" r="6"/><g stroke-linecap="round"><path d="M6 4v4M4 6h4"/></g></g><circle stroke="#primary" stroke-width="2" cx="10.5" cy="10.5" r="10.5"/><g stroke-linecap="round"><path d="M32.01 1.37A23.96 23.96 0 0024 0c-.999 0-1.983.061-2.95.18M.32 20.072a24.21 24.21 0 00.015 7.948M12.42 45.025A23.892 23.892 0 0024 48c13.255 0 24-10.745 24-24 0-2.811-.483-5.51-1.371-8.016" stroke="#secondary" stroke-width="2"/><path stroke="#primary" stroke-width="2" d="M8.999 7.151v5.865"/><path d="M9 3a2 2 0 110 4 2 2 0 010-4zm0 10.8a2 2 0 11-.001 4 2 2 0 01.001-4z" stroke="#primary" stroke-width="1.8"/><path d="M9.622 11.838c.138-.007.989.119 1.595-.05.607-.169 1.584-.539 1.829-1.337" stroke="#primary" stroke-width="2"/><path d="M14.8 7.202a2 2 0 110 4 2 2 0 010-4z" stroke="#primary" stroke-width="1.8"/></g></g>',
|
||||||
...rank(value, [1, 100, 500, 1000, 2500]),
|
...rank(value, [1, 100, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
leaderboard:leaderboard({user:ranks.forks_rank.repositoryCount, requirement:scores.forks >= requirements.forks, type:"repositories"}),
|
leaderboard: leaderboard({user: ranks.forks_rank.repositoryCount, requirement: scores.forks >= requirements.forks, type: "repositories"}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Polyglot
|
//Polyglot
|
||||||
{
|
{
|
||||||
const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node:{name}}) => name))).size
|
const value = new Set(data.user.repositories.nodes.flatMap(repository => repository.languages.edges.map(({node: {name}}) => name))).size
|
||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Polyglot",
|
title: "Polyglot",
|
||||||
text:`Using ${value} different programming language${imports.s(value)}`,
|
text: `Using ${value} different programming language${imports.s(value)}`,
|
||||||
icon:'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M17.135 7.988l-3.303.669a2 2 0 00-1.586 2.223l4.708 35.392a1.498 1.498 0 01-1.162 1.66 1.523 1.523 0 01-1.775-1.01L4.951 19.497a2 2 0 011.215-2.507l2.946-1.072" stroke="#secondary" stroke-linejoin="round"/><path d="M36.8 48H23a2 2 0 01-2-2V7a2 2 0 012-2h26a2 2 0 012 2v32.766" stroke="#primary"/><path d="M29 20.955l-3.399 3.399a.85.85 0 000 1.202l3.399 3.4M43.014 20.955l3.399 3.399a.85.85 0 010 1.202l-3.4 3.4" stroke="#primary" stroke-linejoin="round"/><path stroke="#primary" d="M38.526 18l-5.053 14.016"/><path d="M44 36a8 8 0 110 16 8 8 0 010-16z" stroke="#primary" stroke-linejoin="round"/><path d="M43.068 40.749l3.846 2.396a1 1 0 01-.006 1.7l-3.846 2.36a1 1 0 01-1.523-.853v-4.755a1 1 0 011.529-.848z" stroke="#primary" stroke-linejoin="round"/></g>',
|
icon:
|
||||||
|
'<g stroke-linecap="round" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M17.135 7.988l-3.303.669a2 2 0 00-1.586 2.223l4.708 35.392a1.498 1.498 0 01-1.162 1.66 1.523 1.523 0 01-1.775-1.01L4.951 19.497a2 2 0 011.215-2.507l2.946-1.072" stroke="#secondary" stroke-linejoin="round"/><path d="M36.8 48H23a2 2 0 01-2-2V7a2 2 0 012-2h26a2 2 0 012 2v32.766" stroke="#primary"/><path d="M29 20.955l-3.399 3.399a.85.85 0 000 1.202l3.399 3.4M43.014 20.955l3.399 3.399a.85.85 0 010 1.202l-3.4 3.4" stroke="#primary" stroke-linejoin="round"/><path stroke="#primary" d="M38.526 18l-5.053 14.016"/><path d="M44 36a8 8 0 110 16 8 8 0 010-16z" stroke="#primary" stroke-linejoin="round"/><path d="M43.068 40.749l3.846 2.396a1 1 0 01-.006 1.7l-3.846 2.36a1 1 0 01-1.523-.853v-4.755a1 1 0 011.529-.848z" stroke="#primary" stroke-linejoin="round"/></g>',
|
||||||
...rank(value, [1, 4, 8, 16, 32]),
|
...rank(value, [1, 4, 8, 16, 32]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Member
|
//Member
|
||||||
{
|
{
|
||||||
const {years:value} = computed.registered
|
const {years: value} = computed.registered
|
||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Member",
|
title: "Member",
|
||||||
text:`Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`,
|
text: `Registered ${Math.floor(value)} year${imports.s(Math.floor(value))} ago`,
|
||||||
icon:'<g xmlns="http://www.w3.org/2000/svg" transform="translate(5 4)" fill="none" fill-rule="evenodd"><path d="M46 44.557v1a2 2 0 01-2 2H2a2 2 0 01-2-2v-1" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M.75 40.993l.701.561a2.323 2.323 0 002.903 0l1.675-1.34a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.103.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.429 1.182a2.427 2.427 0 003.103-.008l.832-.695A2 2 0 0046 39.191v-1.634a2 2 0 00-2-2H2a2 2 0 00-2 2v1.875a2 2 0 00.75 1.561z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M42 31.609v.948m-38 0v-.992m25.04-15.008H35a2 2 0 012 2v1m-28 0v-1a2 2 0 012-2h6.007" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 8.557h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6a1 1 0 011-1z" stroke="#primary" stroke-width="2" stroke-linejoin="round"/><path d="M4.7 10.557c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zm35-8c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M23 5.557a2 2 0 002-2C25 2.452 24.433 0 22.273 0c-.463 0 .21 1.424-.502 1.979A2 2 0 0023 5.557z" stroke="#primary" stroke-width="2"/><path d="M4.78 27.982l1.346 1.076a3 3 0 003.748 0l1.252-1.002a3 3 0 013.748 0l1.282 1.026a3 3 0 003.711.03l1.4-1.085a3 3 0 013.75.061l1.102.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.281 1.025a3 3 0 003.712.029l1.358-1.053a2 2 0 00.775-1.58v-.97a1.95 1.95 0 00-1.95-1.95H5.942a1.912 1.912 0 00-1.912 1.912v.951a2 2 0 00.75 1.562z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" cx="16.5" cy="2.057" r="1"/><circle stroke="#secondary" cx="14.5" cy="12.057" r="1"/><circle stroke="#secondary" cx="31.5" cy="9.057" r="1"/></g>',
|
icon:
|
||||||
|
'<g xmlns="http://www.w3.org/2000/svg" transform="translate(5 4)" fill="none" fill-rule="evenodd"><path d="M46 44.557v1a2 2 0 01-2 2H2a2 2 0 01-2-2v-1" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M.75 40.993l.701.561a2.323 2.323 0 002.903 0l1.675-1.34a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.103.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.282 1.026a3 3 0 003.71.03l1.4-1.085a3 3 0 013.75.061l1.429 1.182a2.427 2.427 0 003.103-.008l.832-.695A2 2 0 0046 39.191v-1.634a2 2 0 00-2-2H2a2 2 0 00-2 2v1.875a2 2 0 00.75 1.561z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M42 31.609v.948m-38 0v-.992m25.04-15.008H35a2 2 0 012 2v1m-28 0v-1a2 2 0 012-2h6.007" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M22 8.557h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6a1 1 0 011-1z" stroke="#primary" stroke-width="2" stroke-linejoin="round"/><path d="M4.7 10.557c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zm35-8c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M23 5.557a2 2 0 002-2C25 2.452 24.433 0 22.273 0c-.463 0 .21 1.424-.502 1.979A2 2 0 0023 5.557z" stroke="#primary" stroke-width="2"/><path d="M4.78 27.982l1.346 1.076a3 3 0 003.748 0l1.252-1.002a3 3 0 013.748 0l1.282 1.026a3 3 0 003.711.03l1.4-1.085a3 3 0 013.75.061l1.102.913a3 3 0 003.787.031l1.22-.976a3 3 0 013.748 0l1.281 1.025a3 3 0 003.712.029l1.358-1.053a2 2 0 00.775-1.58v-.97a1.95 1.95 0 00-1.95-1.95H5.942a1.912 1.912 0 00-1.912 1.912v.951a2 2 0 00.75 1.562z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" cx="16.5" cy="2.057" r="1"/><circle stroke="#secondary" cx="14.5" cy="12.057" r="1"/><circle stroke="#secondary" cx="31.5" cy="9.057" r="1"/></g>',
|
||||||
...rank(value, [1, 3, 5, 10, 15]),
|
...rank(value, [1, 3, 5, 10, 15]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,12 +253,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Sponsor",
|
title: "Sponsor",
|
||||||
text:`Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`,
|
text: `Sponsoring ${value} user${imports.s(value)} or organization${imports.s(value)}`,
|
||||||
icon:'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M24 32c.267-1.727 1.973-3 4-3 2.08 0 3.787 1.318 4 3m-4-9a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M46.138 15c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C41.347 15 41 16.117 41 17.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm-31-5c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C10.347 10 10 11.117 10 12.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm6 32c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C16.347 42 16 43.117 16 44.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005z" fill="#secondary"/><path d="M8.003 29a3 3 0 110 6 3 3 0 010-6zM32.018 5.005a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path stroke="#secondary" stroke-width="2" d="M29.972 18.026L31.361 11M18.063 29.987l-7.004 1.401"/><path d="M22.604 11.886l.746 2.164m-9.313 9.296l-2.156-.712" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21.304 9a1 1 0 100-2 1 1 0 000 2zM8.076 22.346a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M33.267 44.17l-.722-2.146m9.38-9.206l2.147.743" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M34.544 49.031a1 1 0 100-2 1 1 0 000 2zm13.314-13.032a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M48.019 51.004a3 3 0 100-6 3 3 0 000 6zM35.194 35.33l10.812 11.019" stroke="#secondary" stroke-width="2"/></g>',
|
icon:
|
||||||
|
'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M24 32c.267-1.727 1.973-3 4-3 2.08 0 3.787 1.318 4 3m-4-9a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 18c5.523 0 10 4.477 10 10s-4.477 10-10 10-10-4.477-10-10 4.477-10 10-10z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M46.138 15c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C41.347 15 41 16.117 41 17.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm-31-5c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C10.347 10 10 11.117 10 12.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005zm6 32c-1.033 0-1.454.822-1.634 1.413-.019.06-.024.06-.042 0-.182-.591-.707-1.413-1.655-1.413C16.347 42 16 43.117 16 44.005c0 1.676 2.223 3.228 3.091 3.845.272.197.556.194.817 0 .798-.593 3.092-2.17 3.092-3.845 0-.888-.261-2.005-1.862-2.005z" fill="#secondary"/><path d="M8.003 29a3 3 0 110 6 3 3 0 010-6zM32.018 5.005a3 3 0 110 6 3 3 0 010-6z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path stroke="#secondary" stroke-width="2" d="M29.972 18.026L31.361 11M18.063 29.987l-7.004 1.401"/><path d="M22.604 11.886l.746 2.164m-9.313 9.296l-2.156-.712" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M21.304 9a1 1 0 100-2 1 1 0 000 2zM8.076 22.346a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M33.267 44.17l-.722-2.146m9.38-9.206l2.147.743" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M34.544 49.031a1 1 0 100-2 1 1 0 000 2zm13.314-13.032a1 1 0 100-2 1 1 0 000 2z" fill="#primary"/><path d="M48.019 51.004a3 3 0 100-6 3 3 0 000 6zM35.194 35.33l10.812 11.019" stroke="#secondary" stroke-width="2"/></g>',
|
||||||
...rank(value, [1, 3, 5, 10, 25]),
|
...rank(value, [1, 3, 5, 10, 25]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,12 +269,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Deployer",
|
title: "Deployer",
|
||||||
text:`Repositories have been deployed ${value} time${imports.s(value)}`,
|
text: `Repositories have been deployed ${value} time${imports.s(value)}`,
|
||||||
icon:'<g stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary"><path d="M11 40a2 2 0 100-4 2 2 0 000 4z"/><path d="M11 34v1m0 5v3m0 3v3" stroke-linecap="round"/></g><g stroke="#secondary" stroke-linecap="round" stroke-linejoin="round"><path d="M47.01 41.009h-4M45.016 39v4"/></g><path d="M27.982 5c2.79 1.873 4.46 5.876 5.008 12.01l2.059.659a1.606 1.606 0 011.606-1.665h.84a2.513 2.513 0 012.511 2.508l.004 1.496 3.197 1.588a1.951 1.951 0 011.684-.952l.509.002c.898.003 1.625.73 1.629 1.629l.008 2.115L51 27.606v1.945l-4.815-1.211c-.474.894-.87 1.48-1.192 1.757-.345-.328-.814-1.158-1.406-2.49L38.744 26.5c-.402 1.153-.845 1.828-1.328 2.026-.451-.444-1.04-1.55-1.409-2.821-1.481-.286-2.486-.56-2.994-.688-.27 2.397-1.036 6.884-2.009 10.982l5.006 4.438-6.555-1.08-1.454 3.654-1.45-3.658-6.56 1.082 4.996-4.417c-.899-4.02-1.576-7.684-2.03-10.992a37.29 37.29 0 01-2.967.679c-.38 1.252-.845 2.191-1.396 2.817-.63-.184-1.142-1.474-1.338-2.023-.705.15-2.323.519-4.853 1.107-.601 1.388-1.07 2.218-1.41 2.49a7.032 7.032 0 01-1.173-1.758L5 29.55v-1.945l3.99-3.265v-2.102a1.604 1.604 0 011.625-1.604l.528.007c.68.008 1.307.37 1.654.956l3.184-1.614.003-1.467a2.503 2.503 0 012.511-2.497l.863.003a1.6 1.6 0 011.594 1.646 62.42 62.42 0 012.024-.667c.572-6.097 2.24-10.098 5.006-12.002z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#secondary" stroke-linecap="round" d="M45.016 36.032v-2M45.016 49.032v-3M38.978 36.089v-3.153M17.016 36.089v-3.153M51.031 51.165v-2.193m0-2.972V35.013M4.974 51.165v-2.193m0-2.972V35.013"/></g>',
|
icon:
|
||||||
|
'<g stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary"><path d="M11 40a2 2 0 100-4 2 2 0 000 4z"/><path d="M11 34v1m0 5v3m0 3v3" stroke-linecap="round"/></g><g stroke="#secondary" stroke-linecap="round" stroke-linejoin="round"><path d="M47.01 41.009h-4M45.016 39v4"/></g><path d="M27.982 5c2.79 1.873 4.46 5.876 5.008 12.01l2.059.659a1.606 1.606 0 011.606-1.665h.84a2.513 2.513 0 012.511 2.508l.004 1.496 3.197 1.588a1.951 1.951 0 011.684-.952l.509.002c.898.003 1.625.73 1.629 1.629l.008 2.115L51 27.606v1.945l-4.815-1.211c-.474.894-.87 1.48-1.192 1.757-.345-.328-.814-1.158-1.406-2.49L38.744 26.5c-.402 1.153-.845 1.828-1.328 2.026-.451-.444-1.04-1.55-1.409-2.821-1.481-.286-2.486-.56-2.994-.688-.27 2.397-1.036 6.884-2.009 10.982l5.006 4.438-6.555-1.08-1.454 3.654-1.45-3.658-6.56 1.082 4.996-4.417c-.899-4.02-1.576-7.684-2.03-10.992a37.29 37.29 0 01-2.967.679c-.38 1.252-.845 2.191-1.396 2.817-.63-.184-1.142-1.474-1.338-2.023-.705.15-2.323.519-4.853 1.107-.601 1.388-1.07 2.218-1.41 2.49a7.032 7.032 0 01-1.173-1.758L5 29.55v-1.945l3.99-3.265v-2.102a1.604 1.604 0 011.625-1.604l.528.007c.68.008 1.307.37 1.654.956l3.184-1.614.003-1.467a2.503 2.503 0 012.511-2.497l.863.003a1.6 1.6 0 011.594 1.646 62.42 62.42 0 012.024-.667c.572-6.097 2.24-10.098 5.006-12.002z" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#secondary" stroke-linecap="round" d="M45.016 36.032v-2M45.016 49.032v-3M38.978 36.089v-3.153M17.016 36.089v-3.153M51.031 51.165v-2.193m0-2.972V35.013M4.974 51.165v-2.193m0-2.972V35.013"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,12 +285,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Chatter",
|
title: "Chatter",
|
||||||
text:`Participated in discussions ${value} time${imports.s(value)}`,
|
text: `Participated in discussions ${value} time${imports.s(value)}`,
|
||||||
icon:'<g fill="none" fill-rule="evenodd"><path d="M6 42c.45-3.415 3.34-6 7-6 1.874 0 3.752.956 5 3m-6-13a5 5 0 110 10 5 5 0 010-10zm38 16c-.452-3.415-3.34-6-7-6-1.874 0-3.752.956-5 3m6-13a5 5 0 100 10 5 5 0 000-10z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M37 51c-.92-4.01-4.6-7-9-7-4.401 0-8.083 2.995-9 7" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28.01 31.004a6.5 6.5 0 110 13 6.5 6.5 0 010-13z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><path d="M28 14.011a5 5 0 11-5 4.998 5 5 0 015-4.998z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M22 26c1.558-1.25 3.665-2 6-2 2.319 0 4.439.761 6 2" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M51 9V8c0-1.3-1.574-3-3-3h-8c-1.426 0-3 1.7-3 3v13l4-4h6c2.805-.031 4-1.826 4-4V9zM5 9V8c0-1.3 1.574-3 3-3h8c1.426 0 3 1.7 3 3v13l-4-4H9c-2.805-.031-4-1.826-4-4V9z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M43 11a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0zm-36 0a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0z" fill="#primary"/></g>',
|
icon:
|
||||||
|
'<g fill="none" fill-rule="evenodd"><path d="M6 42c.45-3.415 3.34-6 7-6 1.874 0 3.752.956 5 3m-6-13a5 5 0 110 10 5 5 0 010-10zm38 16c-.452-3.415-3.34-6-7-6-1.874 0-3.752.956-5 3m6-13a5 5 0 100 10 5 5 0 000-10z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M37 51c-.92-4.01-4.6-7-9-7-4.401 0-8.083 2.995-9 7" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M28.01 31.004a6.5 6.5 0 110 13 6.5 6.5 0 010-13z" stroke="#primary" stroke-width="2" stroke-linecap="round"/><path d="M28 14.011a5 5 0 11-5 4.998 5 5 0 015-4.998z" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M22 26c1.558-1.25 3.665-2 6-2 2.319 0 4.439.761 6 2" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M51 9V8c0-1.3-1.574-3-3-3h-8c-1.426 0-3 1.7-3 3v13l4-4h6c2.805-.031 4-1.826 4-4V9zM5 9V8c0-1.3 1.574-3 3-3h8c1.426 0 3 1.7 3 3v13l-4-4H9c-2.805-.031-4-1.826-4-4V9z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M43 11a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0zm-36 0a1 1 0 11-2 0 1 1 0 012 0zm4 0a1 1 0 11-2 0 1 1 0 012 0z" fill="#primary"/></g>',
|
||||||
...rank(value, [1, 200, 500, 1000, 2500]),
|
...rank(value, [1, 200, 500, 1000, 2500]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,12 +301,13 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Helper",
|
title: "Helper",
|
||||||
text:`Answered and solved ${value} discussion${imports.s(value)}`,
|
text: `Answered and solved ${value} discussion${imports.s(value)}`,
|
||||||
icon:'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M28 37c1.003.005.997-.443 1-1 .004-1.458-.004-4.629 0-6-.007-.564-.068-.987-1-1-2.118 0-4 1.79-4 4s1.882 4 4 4zM48 37c-1.003.005-.997-.443-1-1-.004-1.458.004-4.629 0-6 .007-.564.068-.987 1-1 2.118 0 4 1.79 4 4s-1.882 4-4 4z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M26 51c.798-4.48 4.87-8.082 9.97-8.849.66-.1 1.338-.151 2.028-.151m9.606 4.038c1.27 1.408 2.12 3.102 2.396 4.945M32.002 39.71A8.966 8.966 0 0038 42a8.967 8.967 0 006.02-2.31m-.015-12.394A8.967 8.967 0 0038 25a8.966 8.966 0 00-5.994 2.286" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M38 45c.17 0 .34-.004.509-.01 5.23-.219 9.596-3.785 11.01-8.613m-.004-6.766C48.052 24.634 43.45 21 38 21c-5.485 0-10.11 3.68-11.542 8.706" stroke="#primary" stroke-width="2"/><path d="M37.85 44h1.173c.54 0 .977.438.977.977v.523a1.5 1.5 0 01-3 0v-.65c0-.47.38-.85.85-.85z" fill="#primary"/><path d="M22 27h-3v4l-6-4H7a2 2 0 01-2-2V7a2 2 0 012-2h33a2 2 0 012 2v10" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#primary" stroke-linecap="round" stroke-width="2"><path stroke-linejoin="round" d="M17 13l-3 3 3 3M30 13l3 3-3 3"/><path d="M25.03 10.986l-3.015 10.027"/></g></g>',
|
icon:
|
||||||
|
'<g xmlns="http://www.w3.org/2000/svg" fill="none" fill-rule="evenodd"><path d="M28 37c1.003.005.997-.443 1-1 .004-1.458-.004-4.629 0-6-.007-.564-.068-.987-1-1-2.118 0-4 1.79-4 4s1.882 4 4 4zM48 37c-1.003.005-.997-.443-1-1-.004-1.458.004-4.629 0-6 .007-.564.068-.987 1-1 2.118 0 4 1.79 4 4s-1.882 4-4 4z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M26 51c.798-4.48 4.87-8.082 9.97-8.849.66-.1 1.338-.151 2.028-.151m9.606 4.038c1.27 1.408 2.12 3.102 2.396 4.945M32.002 39.71A8.966 8.966 0 0038 42a8.967 8.967 0 006.02-2.31m-.015-12.394A8.967 8.967 0 0038 25a8.966 8.966 0 00-5.994 2.286" stroke="#secondary" stroke-width="2" stroke-linecap="round"/><path d="M38 45c.17 0 .34-.004.509-.01 5.23-.219 9.596-3.785 11.01-8.613m-.004-6.766C48.052 24.634 43.45 21 38 21c-5.485 0-10.11 3.68-11.542 8.706" stroke="#primary" stroke-width="2"/><path d="M37.85 44h1.173c.54 0 .977.438.977.977v.523a1.5 1.5 0 01-3 0v-.65c0-.47.38-.85.85-.85z" fill="#primary"/><path d="M22 27h-3v4l-6-4H7a2 2 0 01-2-2V7a2 2 0 012-2h33a2 2 0 012 2v10" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g stroke="#primary" stroke-linecap="round" stroke-width="2"><path stroke-linejoin="round" d="M17 13l-3 3 3 3M30 13l3 3-3 3"/><path d="M25.03 10.986l-3.015 10.027"/></g></g>',
|
||||||
...rank(value, [1, 20, 50, 100, 250]),
|
...rank(value, [1, 20, 50, 100, 250]),
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,13 +317,14 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Verified",
|
title: "Verified",
|
||||||
text:"Registered a GPG key to sign commits",
|
text: "Registered a GPG key to sign commits",
|
||||||
icon:'<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M46 17.036v13.016c0 4.014-.587 8.94-4.751 13.67-5.787 5.911-12.816 8.279-13.243 8.283-.426.003-7.91-2.639-13.222-8.283C10.718 39.4 10 34.056 10 30.052V17.036a2 2 0 012-2h32a2 2 0 012 2zM16 15c0-6.616 5.384-12 12-12s12 5.384 12 12" stroke="#secondary"/><path d="M21 15c0-3.744 3.141-7 7-7 3.86 0 7 3.256 7 7m4.703 29.63l-3.672-3.647m-17.99-17.869l-7.127-7.081" stroke="#secondary"/><path d="M28 23a8 8 0 110 16 8 8 0 010-16z" stroke="#primary"/><path stroke="#primary" d="M30.966 29.005l-4 3.994-2.002-1.995"/></g>',
|
icon:
|
||||||
rank:value ? "$" : "X",
|
'<g stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" fill-rule="evenodd"><path d="M46 17.036v13.016c0 4.014-.587 8.94-4.751 13.67-5.787 5.911-12.816 8.279-13.243 8.283-.426.003-7.91-2.639-13.222-8.283C10.718 39.4 10 34.056 10 30.052V17.036a2 2 0 012-2h32a2 2 0 012 2zM16 15c0-6.616 5.384-12 12-12s12 5.384 12 12" stroke="#secondary"/><path d="M21 15c0-3.744 3.141-7 7-7 3.86 0 7 3.256 7 7m4.703 29.63l-3.672-3.647m-17.99-17.869l-7.127-7.081" stroke="#secondary"/><path d="M28 23a8 8 0 110 16 8 8 0 010-16z" stroke="#primary"/><path stroke="#primary" d="M30.966 29.005l-4 3.994-2.002-1.995"/></g>',
|
||||||
progress:value ? 1 : 0,
|
rank: value ? "$" : "X",
|
||||||
|
progress: value ? 1 : 0,
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,13 +334,14 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Explorer",
|
title: "Explorer",
|
||||||
text:"Starred a topic on GitHub Explore",
|
text: "Starred a topic on GitHub Explore",
|
||||||
icon:'<g transform="translate(3 4)" fill="none" fill-rule="evenodd"><path d="M10 37.5l.049.073a2 2 0 002.506.705l24.391-11.324a2 2 0 00.854-2.874l-2.668-4.27a2 2 0 00-2.865-.562L10.463 34.947A1.869 1.869 0 0010 37.5zM33.028 28.592l-4.033-6.58" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linejoin="round" d="M15.52 37.004l-2.499-3.979"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M25.008 48.011l.013-15.002M17.984 47.038l6.996-14.035M32.005 47.029l-6.987-14.016"/><path d="M2.032 17.015A23.999 23.999 0 001 24c0 9.3 5.29 17.365 13.025 21.35m22-.027C43.734 41.33 49 33.28 49 24a24 24 0 00-1.025-6.96M34.022 1.754A23.932 23.932 0 0025 0c-2.429 0-4.774.36-6.983 1.032" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M40.64 8.472c-1.102-2.224-.935-4.764 1.382-6.465-.922-.087-2.209.326-3.004.784a6.024 6.024 0 00-2.674 7.229c.94 2.618 3.982 4.864 7.66 3.64 1.292-.429 2.615-1.508 2.996-2.665-1.8.625-5.258-.3-6.36-2.523zM21.013 6.015c-.22-.802-3.018-1.295-4.998-.919M4.998 8.006C2.25 9.22.808 11.146 1.011 12.009" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" stroke-width="2" cx="11" cy="9" r="6"/><path d="M.994 12.022c.351 1.38 5.069 1.25 10.713-.355 5.644-1.603 9.654-4.273 9.303-5.653" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M26.978 10.105c.318 1.123.573 1.373 1.71 1.679-1.135.314-1.388.566-1.698 1.69-.318-1.122-.573-1.373-1.711-1.679 1.135-.314 1.39-.566 1.7-1.69" fill="#secondary"/><path d="M26.978 10.105c.318 1.123.573 1.373 1.71 1.679-1.135.314-1.388.566-1.698 1.69-.318-1.122-.573-1.373-1.711-1.679 1.135-.314 1.39-.566 1.7-1.69z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.929 22.737c.317 1.121.573 1.372 1.71 1.678-1.135.314-1.389.566-1.699 1.69-.318-1.121-.573-1.372-1.71-1.679 1.134-.313 1.389-.566 1.699-1.69" fill="#secondary"/><path d="M9.929 22.737c.317 1.121.573 1.372 1.71 1.678-1.135.314-1.389.566-1.699 1.69-.318-1.121-.573-1.372-1.71-1.679 1.134-.313 1.389-.566 1.699-1.69z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M38.912 33.684c.318 1.122.573 1.373 1.711 1.679-1.136.313-1.39.565-1.7 1.69-.317-1.123-.573-1.372-1.71-1.68 1.136-.313 1.389-.565 1.7-1.689" fill="#secondary"/><path d="M38.912 33.684c.318 1.122.573 1.373 1.711 1.679-1.136.313-1.39.565-1.7 1.69-.317-1.123-.573-1.372-1.71-1.68 1.136-.313 1.389-.565 1.7-1.689z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g>',
|
icon:
|
||||||
rank:value ? "$" : "X",
|
'<g transform="translate(3 4)" fill="none" fill-rule="evenodd"><path d="M10 37.5l.049.073a2 2 0 002.506.705l24.391-11.324a2 2 0 00.854-2.874l-2.668-4.27a2 2 0 00-2.865-.562L10.463 34.947A1.869 1.869 0 0010 37.5zM33.028 28.592l-4.033-6.58" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#primary" stroke-width="2" stroke-linejoin="round" d="M15.52 37.004l-2.499-3.979"/><path stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M25.008 48.011l.013-15.002M17.984 47.038l6.996-14.035M32.005 47.029l-6.987-14.016"/><path d="M2.032 17.015A23.999 23.999 0 001 24c0 9.3 5.29 17.365 13.025 21.35m22-.027C43.734 41.33 49 33.28 49 24a24 24 0 00-1.025-6.96M34.022 1.754A23.932 23.932 0 0025 0c-2.429 0-4.774.36-6.983 1.032" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M40.64 8.472c-1.102-2.224-.935-4.764 1.382-6.465-.922-.087-2.209.326-3.004.784a6.024 6.024 0 00-2.674 7.229c.94 2.618 3.982 4.864 7.66 3.64 1.292-.429 2.615-1.508 2.996-2.665-1.8.625-5.258-.3-6.36-2.523zM21.013 6.015c-.22-.802-3.018-1.295-4.998-.919M4.998 8.006C2.25 9.22.808 11.146 1.011 12.009" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle stroke="#secondary" stroke-width="2" cx="11" cy="9" r="6"/><path d="M.994 12.022c.351 1.38 5.069 1.25 10.713-.355 5.644-1.603 9.654-4.273 9.303-5.653" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M26.978 10.105c.318 1.123.573 1.373 1.71 1.679-1.135.314-1.388.566-1.698 1.69-.318-1.122-.573-1.373-1.711-1.679 1.135-.314 1.39-.566 1.7-1.69" fill="#secondary"/><path d="M26.978 10.105c.318 1.123.573 1.373 1.71 1.679-1.135.314-1.388.566-1.698 1.69-.318-1.122-.573-1.373-1.711-1.679 1.135-.314 1.39-.566 1.7-1.69z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M9.929 22.737c.317 1.121.573 1.372 1.71 1.678-1.135.314-1.389.566-1.699 1.69-.318-1.121-.573-1.372-1.71-1.679 1.134-.313 1.389-.566 1.699-1.69" fill="#secondary"/><path d="M9.929 22.737c.317 1.121.573 1.372 1.71 1.678-1.135.314-1.389.566-1.699 1.69-.318-1.121-.573-1.372-1.71-1.679 1.134-.313 1.389-.566 1.699-1.69z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M38.912 33.684c.318 1.122.573 1.373 1.711 1.679-1.136.313-1.39.565-1.7 1.69-.317-1.123-.573-1.372-1.71-1.68 1.136-.313 1.389-.565 1.7-1.689" fill="#secondary"/><path d="M38.912 33.684c.318 1.122.573 1.373 1.711 1.679-1.136.313-1.39.565-1.7 1.69-.317-1.123-.573-1.372-1.71-1.68 1.136-.313 1.389-.565 1.7-1.689z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></g>',
|
||||||
progress:value ? 1 : 0,
|
rank: value ? "$" : "X",
|
||||||
|
progress: value ? 1 : 0,
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,45 +351,48 @@ export default async function({list, login, data, computed, imports, graphql, qu
|
|||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Automator",
|
title: "Automator",
|
||||||
text:"Use GitHub Actions to automate profile updates",
|
text: "Use GitHub Actions to automate profile updates",
|
||||||
icon:'<g transform="translate(4 5)" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke-linecap="round" stroke-linejoin="round"><path stroke="#primary" d="M26.478 22l.696 2.087 3.478.696v2.782l-3.478 1.392-.696 1.39 1.392 3.48-1.392 1.39L23 33.827l-1.391.695L20.217 38h-2.782l-1.392-3.478-1.39-.696-3.48 1.391-1.39-1.39 1.39-3.48-.695-1.39L7 27.565v-2.782l3.478-1.392.696-1.391-1.391-3.478 1.39-1.392 3.48 1.392 1.39-.696 1.392-3.478h2.782l1.392 3.478 1.391.696 3.478-1.392 1.392 1.392z"/><path stroke="#secondary" d="M24.779 12.899l-1.475-2.212 1.475-1.475 2.95 1.475 1.474-.738.737-2.934h2.212l.737 2.934 1.475.738 2.95-1.475 1.474 1.475-1.475 2.949.738 1.475 2.949.737v2.212l-2.95.737-.737 1.475 1.475 2.949-1.475 1.475-2.949-1.475"/></g><path stroke="#primary" stroke-linecap="round" d="M5.932 5.546l7.082 6.931"/><path stroke="#secondary" stroke-linecap="round" d="M32.959 31.99l8.728 8.532"/><circle stroke="#secondary" cx="44" cy="43" r="3"/><circle stroke="#primary" cx="13" cy="2" r="2"/><circle stroke="#secondary" cx="35" cy="44" r="2"/><circle stroke="#secondary" cx="3" cy="12" r="2"/><circle stroke="#primary" cx="45" cy="34" r="2"/><path d="M3.832 0a3 3 0 110 6 3 3 0 010-6zM8.04 10.613l2.1-.613M10.334 9.758l1.914-5.669" stroke="#primary" stroke-linecap="round"/><path stroke="#secondary" stroke-linecap="round" d="M40.026 35.91l-2.025.591M35.695 41.965l1.843-5.326"/><path d="M16 2h23.038a6 6 0 016 6v24.033" stroke="#primary" stroke-linecap="round"/><path d="M32.038 44.033H9a6 6 0 01-6-6V14" stroke="#secondary" stroke-linecap="round"/><path d="M17.533 22.154l5.113 3.22a1 1 0 01-.006 1.697l-5.113 3.17a1 1 0 01-1.527-.85V23a1 1 0 011.533-.846zm11.58-7.134v-.504a1 1 0 011.53-.85l3.845 2.397a1 1 0 01-.006 1.701l-3.846 2.358" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/></g>',
|
icon:
|
||||||
rank:value ? "$" : "X",
|
'<g transform="translate(4 5)" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke-linecap="round" stroke-linejoin="round"><path stroke="#primary" d="M26.478 22l.696 2.087 3.478.696v2.782l-3.478 1.392-.696 1.39 1.392 3.48-1.392 1.39L23 33.827l-1.391.695L20.217 38h-2.782l-1.392-3.478-1.39-.696-3.48 1.391-1.39-1.39 1.39-3.48-.695-1.39L7 27.565v-2.782l3.478-1.392.696-1.391-1.391-3.478 1.39-1.392 3.48 1.392 1.39-.696 1.392-3.478h2.782l1.392 3.478 1.391.696 3.478-1.392 1.392 1.392z"/><path stroke="#secondary" d="M24.779 12.899l-1.475-2.212 1.475-1.475 2.95 1.475 1.474-.738.737-2.934h2.212l.737 2.934 1.475.738 2.95-1.475 1.474 1.475-1.475 2.949.738 1.475 2.949.737v2.212l-2.95.737-.737 1.475 1.475 2.949-1.475 1.475-2.949-1.475"/></g><path stroke="#primary" stroke-linecap="round" d="M5.932 5.546l7.082 6.931"/><path stroke="#secondary" stroke-linecap="round" d="M32.959 31.99l8.728 8.532"/><circle stroke="#secondary" cx="44" cy="43" r="3"/><circle stroke="#primary" cx="13" cy="2" r="2"/><circle stroke="#secondary" cx="35" cy="44" r="2"/><circle stroke="#secondary" cx="3" cy="12" r="2"/><circle stroke="#primary" cx="45" cy="34" r="2"/><path d="M3.832 0a3 3 0 110 6 3 3 0 010-6zM8.04 10.613l2.1-.613M10.334 9.758l1.914-5.669" stroke="#primary" stroke-linecap="round"/><path stroke="#secondary" stroke-linecap="round" d="M40.026 35.91l-2.025.591M35.695 41.965l1.843-5.326"/><path d="M16 2h23.038a6 6 0 016 6v24.033" stroke="#primary" stroke-linecap="round"/><path d="M32.038 44.033H9a6 6 0 01-6-6V14" stroke="#secondary" stroke-linecap="round"/><path d="M17.533 22.154l5.113 3.22a1 1 0 01-.006 1.697l-5.113 3.17a1 1 0 01-1.527-.85V23a1 1 0 011.533-.846zm11.58-7.134v-.504a1 1 0 011.53-.85l3.845 2.397a1 1 0 01-.006 1.701l-3.846 2.358" stroke="#primary" stroke-linecap="round" stroke-linejoin="round"/></g>',
|
||||||
progress:value ? 1 : 0,
|
rank: value ? "$" : "X",
|
||||||
|
progress: value ? 1 : 0,
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Infographile
|
//Infographile
|
||||||
{
|
{
|
||||||
const {repository:{viewerHasStarred:value}, viewer:{login:_login}} = await graphql(queries.achievements.metrics())
|
const {repository: {viewerHasStarred: value}, viewer: {login: _login}} = await graphql(queries.achievements.metrics())
|
||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Infographile",
|
title: "Infographile",
|
||||||
text:"Fervent supporter of metrics",
|
text: "Fervent supporter of metrics",
|
||||||
icon:'<g stroke-linejoin="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary" stroke-linecap="round"><path d="M22 31h20M22 36h10"/></g><path d="M44.05 36.013a8 8 0 110 16 8 8 0 010-16z" stroke="#primary" stroke-linecap="round"/><path d="M32 43H7c-1.228 0-2-.84-2-2V7c0-1.16.772-2 2-2h7.075M47 24.04V32" stroke="#secondary" stroke-linecap="round"/><path stroke="#primary" stroke-linecap="round" d="M47.015 42.017l-4 3.994-2.001-1.995"/><path stroke="#secondary" d="M11 31h5v5h-5z"/><path d="M11 14a2 2 0 012-2m28 12a2 2 0 01-2 2h-1m-5 0h-4m-6 0h-4m-5 0h-1a2 2 0 01-2-2m0-4v-2" stroke="#secondary" stroke-linecap="round"/><path d="M18 18V7c0-1.246.649-2 1.73-2h28.54C49.351 5 50 5.754 50 7v11c0 1.246-.649 2-1.73 2H19.73c-1.081 0-1.73-.754-1.73-2z" stroke="#primary" stroke-linecap="round"/><path stroke="#primary" stroke-linecap="round" d="M22 13h4l2-3 3 5 2-2h3.052l2.982-4 3.002 4H46"/></g>',
|
icon:
|
||||||
rank:(value) && (login === _login) ? "$" : "X",
|
'<g stroke-linejoin="round" stroke-width="2" fill="none" fill-rule="evenodd"><g stroke="#secondary" stroke-linecap="round"><path d="M22 31h20M22 36h10"/></g><path d="M44.05 36.013a8 8 0 110 16 8 8 0 010-16z" stroke="#primary" stroke-linecap="round"/><path d="M32 43H7c-1.228 0-2-.84-2-2V7c0-1.16.772-2 2-2h7.075M47 24.04V32" stroke="#secondary" stroke-linecap="round"/><path stroke="#primary" stroke-linecap="round" d="M47.015 42.017l-4 3.994-2.001-1.995"/><path stroke="#secondary" d="M11 31h5v5h-5z"/><path d="M11 14a2 2 0 012-2m28 12a2 2 0 01-2 2h-1m-5 0h-4m-6 0h-4m-5 0h-1a2 2 0 01-2-2m0-4v-2" stroke="#secondary" stroke-linecap="round"/><path d="M18 18V7c0-1.246.649-2 1.73-2h28.54C49.351 5 50 5.754 50 7v11c0 1.246-.649 2-1.73 2H19.73c-1.081 0-1.73-.754-1.73-2z" stroke="#primary" stroke-linecap="round"/><path stroke="#primary" stroke-linecap="round" d="M22 13h4l2-3 3 5 2-2h3.052l2.982-4 3.002 4H46"/></g>',
|
||||||
progress:(value) && (login === _login) ? 1 : 0,
|
rank: (value) && (login === _login) ? "$" : "X",
|
||||||
|
progress: (value) && (login === _login) ? 1 : 0,
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Octonaut
|
//Octonaut
|
||||||
{
|
{
|
||||||
const {user:{viewerIsFollowing:value}, viewer:{login:_login}} = await graphql(queries.achievements.octocat())
|
const {user: {viewerIsFollowing: value}, viewer: {login: _login}} = await graphql(queries.achievements.octocat())
|
||||||
const unlock = null
|
const unlock = null
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
title:"Octonaut",
|
title: "Octonaut",
|
||||||
text:"Following octocat",
|
text: "Following octocat",
|
||||||
icon:'<g fill="none" fill-rule="evenodd"><path d="M14.7 8c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zm26 0c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zM28.021 5c.318 1.122.574 1.372 1.711 1.678-1.136.314-1.389.566-1.7 1.69-.317-1.121-.572-1.372-1.71-1.679 1.135-.313 1.39-.566 1.7-1.689z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g transform="translate(4 9)" fill-rule="nonzero"><path d="M14.05 9.195C10.327 7.065 7.46 6 5.453 6 4.92 6 4 6.164 3.5 6.653s-.572.741-.711 1.14c-.734 2.1-1.562 6.317.078 9.286-8.767 25.38 15.513 24.92 21.207 24.92 5.695 0 29.746.456 21.037-24.908 1.112-2.2 1.404-5.119.121-9.284-.863-2.802-4.646-2.341-11.35 1.384a27.38 27.38 0 00-9.802-1.81c-3.358 0-6.701.605-10.03 1.814z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M10.323 40.074c-2.442-1.02-2.93-3.308-2.93-4.834 0-1.527.488-2.45.976-3.92.489-1.47.391-2.281-.976-5.711-1.368-3.43.976-7.535 4.884-7.535 3.908 0 7.088 3.005 11.723 2.956m0 0c4.635.05 7.815-2.956 11.723-2.956 3.908 0 6.252 4.105 4.884 7.535-1.367 3.43-1.465 4.241-.976 5.71.488 1.47.976 2.394.976 3.92 0 1.527-.488 3.816-2.93 4.835" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle fill="#primary" cx="12" cy="30" r="1"/><circle fill="#primary" cx="13" cy="28" r="1"/><circle fill="#primary" cx="15" cy="28" r="1"/><circle fill="#primary" cx="23" cy="35" r="1"/><circle fill="#primary" cx="25" cy="35" r="1"/><circle fill="#primary" cx="17" cy="28" r="1"/><circle fill="#primary" cx="31" cy="28" r="1"/><circle fill="#primary" cx="33" cy="28" r="1"/><circle fill="#primary" cx="35" cy="28" r="1"/><circle fill="#primary" cx="12" cy="32" r="1"/><circle fill="#primary" cx="19" cy="30" r="1"/><circle fill="#primary" cx="19" cy="32" r="1"/><circle fill="#primary" cx="29" cy="30" r="1"/><circle fill="#primary" cx="29" cy="32" r="1"/><circle fill="#primary" cx="36" cy="30" r="1"/><circle fill="#primary" cx="36" cy="32" r="1"/></g></g>',
|
icon:
|
||||||
rank:(value) && (login === _login) ? "$" : "X",
|
'<g fill="none" fill-rule="evenodd"><path d="M14.7 8c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zm26 0c.316 1.122.572 1.372 1.71 1.678-1.136.314-1.39.566-1.7 1.69-.317-1.121-.573-1.372-1.71-1.679 1.135-.313 1.389-.566 1.7-1.689zM28.021 5c.318 1.122.574 1.372 1.711 1.678-1.136.314-1.389.566-1.7 1.69-.317-1.121-.572-1.372-1.71-1.679 1.135-.313 1.39-.566 1.7-1.689z" stroke="#primary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><g transform="translate(4 9)" fill-rule="nonzero"><path d="M14.05 9.195C10.327 7.065 7.46 6 5.453 6 4.92 6 4 6.164 3.5 6.653s-.572.741-.711 1.14c-.734 2.1-1.562 6.317.078 9.286-8.767 25.38 15.513 24.92 21.207 24.92 5.695 0 29.746.456 21.037-24.908 1.112-2.2 1.404-5.119.121-9.284-.863-2.802-4.646-2.341-11.35 1.384a27.38 27.38 0 00-9.802-1.81c-3.358 0-6.701.605-10.03 1.814z" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M10.323 40.074c-2.442-1.02-2.93-3.308-2.93-4.834 0-1.527.488-2.45.976-3.92.489-1.47.391-2.281-.976-5.711-1.368-3.43.976-7.535 4.884-7.535 3.908 0 7.088 3.005 11.723 2.956m0 0c4.635.05 7.815-2.956 11.723-2.956 3.908 0 6.252 4.105 4.884 7.535-1.367 3.43-1.465 4.241-.976 5.71.488 1.47.976 2.394.976 3.92 0 1.527-.488 3.816-2.93 4.835" stroke="#secondary" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle fill="#primary" cx="12" cy="30" r="1"/><circle fill="#primary" cx="13" cy="28" r="1"/><circle fill="#primary" cx="15" cy="28" r="1"/><circle fill="#primary" cx="23" cy="35" r="1"/><circle fill="#primary" cx="25" cy="35" r="1"/><circle fill="#primary" cx="17" cy="28" r="1"/><circle fill="#primary" cx="31" cy="28" r="1"/><circle fill="#primary" cx="33" cy="28" r="1"/><circle fill="#primary" cx="35" cy="28" r="1"/><circle fill="#primary" cx="12" cy="32" r="1"/><circle fill="#primary" cx="19" cy="30" r="1"/><circle fill="#primary" cx="19" cy="32" r="1"/><circle fill="#primary" cx="29" cy="30" r="1"/><circle fill="#primary" cx="29" cy="32" r="1"/><circle fill="#primary" cx="36" cy="30" r="1"/><circle fill="#primary" cx="36" cy="32" r="1"/></g></g>',
|
||||||
progress:(value) && (login === _login) ? 1 : 0,
|
rank: (value) && (login === _login) ? "$" : "X",
|
||||||
|
progress: (value) && (login === _login) ? 1 : 0,
|
||||||
value,
|
value,
|
||||||
unlock:new Date(unlock?.createdAt),
|
unlock: new Date(unlock?.createdAt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ export default async function({login, data, rest, q, account, imports}, {enabled
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
let context = {mode:"user"}
|
let context = {mode: "user"}
|
||||||
if (q.repo) {
|
if (q.repo) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > activity > switched to repository mode`)
|
console.debug(`metrics/compute/${login}/plugins > activity > switched to repository mode`)
|
||||||
const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift()
|
const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift()
|
||||||
context = {...context, mode:"repository", owner, repo}
|
context = {...context, mode: "repository", owner, repo}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -29,7 +29,7 @@ export default async function({login, data, rest, q, account, imports}, {enabled
|
|||||||
try {
|
try {
|
||||||
for (let page = 1; page <= pages; page++) {
|
for (let page = 1; page <= pages; page++) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > activity > loading page ${page}/${pages}`)
|
console.debug(`metrics/compute/${login}/plugins > activity > loading page ${page}/${pages}`)
|
||||||
events.push(...(context.mode === "repository" ? await rest.activity.listRepoEvents({owner:context.owner, repo:context.repo}) : await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data)
|
events.push(...(context.mode === "repository" ? await rest.activity.listRepoEvents({owner: context.owner, repo: context.repo}) : await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -43,7 +43,7 @@ export default async function({login, data, rest, q, account, imports}, {enabled
|
|||||||
.filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase())
|
.filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase())
|
||||||
.filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true)
|
.filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true)
|
||||||
.filter(event => visibility === "public" ? event.public : true)
|
.filter(event => visibility === "public" ? event.public : true)
|
||||||
.map(async ({type, payload, actor:{login:actor}, repo:{name:repo}, created_at}) => {
|
.map(async ({type, payload, actor: {login: actor}, repo: {name: repo}, created_at}) => {
|
||||||
//See https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types
|
//See https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types
|
||||||
const timestamp = new Date(created_at)
|
const timestamp = new Date(created_at)
|
||||||
if ((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo)))
|
if ((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo)))
|
||||||
@@ -53,110 +53,110 @@ export default async function({login, data, rest, q, account, imports}, {enabled
|
|||||||
case "CommitCommentEvent": {
|
case "CommitCommentEvent": {
|
||||||
if (!["created"].includes(payload.action))
|
if (!["created"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {comment:{user:{login:user}, commit_id:sha, body:content}} = payload
|
const {comment: {user: {login: user}, commit_id: sha, body: content}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"comment", on:"commit", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile:null, number:sha.substring(0, 7), title:""}
|
return {type: "comment", on: "commit", actor, timestamp, repo, content: await imports.markdown(content, {mode: markdown, codelines}), user, mobile: null, number: sha.substring(0, 7), title: ""}
|
||||||
}
|
}
|
||||||
//Created a git branch or tag
|
//Created a git branch or tag
|
||||||
case "CreateEvent": {
|
case "CreateEvent": {
|
||||||
const {ref:name, ref_type:type} = payload
|
const {ref: name, ref_type: type} = payload
|
||||||
return {type:"ref/create", actor, timestamp, repo, ref:{name, type}}
|
return {type: "ref/create", actor, timestamp, repo, ref: {name, type}}
|
||||||
}
|
}
|
||||||
//Deleted a git branch or tag
|
//Deleted a git branch or tag
|
||||||
case "DeleteEvent": {
|
case "DeleteEvent": {
|
||||||
const {ref:name, ref_type:type} = payload
|
const {ref: name, ref_type: type} = payload
|
||||||
return {type:"ref/delete", actor, timestamp, repo, ref:{name, type}}
|
return {type: "ref/delete", actor, timestamp, repo, ref: {name, type}}
|
||||||
}
|
}
|
||||||
//Forked repository
|
//Forked repository
|
||||||
case "ForkEvent": {
|
case "ForkEvent": {
|
||||||
const {forkee:{full_name:forked}} = payload
|
const {forkee: {full_name: forked}} = payload
|
||||||
return {type:"fork", actor, timestamp, repo, forked}
|
return {type: "fork", actor, timestamp, repo, forked}
|
||||||
}
|
}
|
||||||
//Wiki editions
|
//Wiki editions
|
||||||
case "GollumEvent": {
|
case "GollumEvent": {
|
||||||
const {pages} = payload
|
const {pages} = payload
|
||||||
return {type:"wiki", actor, timestamp, repo, pages:pages.map(({title}) => title)}
|
return {type: "wiki", actor, timestamp, repo, pages: pages.map(({title}) => title)}
|
||||||
}
|
}
|
||||||
//Commented on an issue
|
//Commented on an issue
|
||||||
case "IssueCommentEvent": {
|
case "IssueCommentEvent": {
|
||||||
if (!["created"].includes(payload.action))
|
if (!["created"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {issue:{user:{login:user}, title, number}, comment:{body:content, performed_via_github_app:mobile}} = payload
|
const {issue: {user: {login: user}, title, number}, comment: {body: content, performed_via_github_app: mobile}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"comment", on:"issue", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile, number, title}
|
return {type: "comment", on: "issue", actor, timestamp, repo, content: await imports.markdown(content, {mode: markdown, codelines}), user, mobile, number, title}
|
||||||
}
|
}
|
||||||
//Issue event
|
//Issue event
|
||||||
case "IssuesEvent": {
|
case "IssuesEvent": {
|
||||||
if (!["opened", "closed", "reopened"].includes(payload.action))
|
if (!["opened", "closed", "reopened"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {action, issue:{user:{login:user}, title, number, body:content}} = payload
|
const {action, issue: {user: {login: user}, title, number, body: content}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"issue", actor, timestamp, repo, action, user, number, title, content:await imports.markdown(content, {mode:markdown, codelines})}
|
return {type: "issue", actor, timestamp, repo, action, user, number, title, content: await imports.markdown(content, {mode: markdown, codelines})}
|
||||||
}
|
}
|
||||||
//Activity from repository collaborators
|
//Activity from repository collaborators
|
||||||
case "MemberEvent": {
|
case "MemberEvent": {
|
||||||
if (!["added"].includes(payload.action))
|
if (!["added"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {member:{login:user}} = payload
|
const {member: {login: user}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"member", actor, timestamp, repo, user}
|
return {type: "member", actor, timestamp, repo, user}
|
||||||
}
|
}
|
||||||
//Made repository public
|
//Made repository public
|
||||||
case "PublicEvent": {
|
case "PublicEvent": {
|
||||||
return {type:"public", actor, timestamp, repo}
|
return {type: "public", actor, timestamp, repo}
|
||||||
}
|
}
|
||||||
//Pull requests events
|
//Pull requests events
|
||||||
case "PullRequestEvent": {
|
case "PullRequestEvent": {
|
||||||
if (!["opened", "closed"].includes(payload.action))
|
if (!["opened", "closed"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {action, pull_request:{user:{login:user}, title, number, body:content, additions:added, deletions:deleted, changed_files:changed, merged}} = payload
|
const {action, pull_request: {user: {login: user}, title, number, body: content, additions: added, deletions: deleted, changed_files: changed, merged}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"pr", actor, timestamp, repo, action:(action === "closed") && (merged) ? "merged" : action, user, title, number, content:await imports.markdown(content, {mode:markdown, codelines}), lines:{added, deleted}, files:{changed}}
|
return {type: "pr", actor, timestamp, repo, action: (action === "closed") && (merged) ? "merged" : action, user, title, number, content: await imports.markdown(content, {mode: markdown, codelines}), lines: {added, deleted}, files: {changed}}
|
||||||
}
|
}
|
||||||
//Reviewed a pull request
|
//Reviewed a pull request
|
||||||
case "PullRequestReviewEvent": {
|
case "PullRequestReviewEvent": {
|
||||||
const {review:{state:review}, pull_request:{user:{login:user}, number, title}} = payload
|
const {review: {state: review}, pull_request: {user: {login: user}, number, title}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"review", actor, timestamp, repo, review, user, number, title}
|
return {type: "review", actor, timestamp, repo, review, user, number, title}
|
||||||
}
|
}
|
||||||
//Commented on a pull request
|
//Commented on a pull request
|
||||||
case "PullRequestReviewCommentEvent": {
|
case "PullRequestReviewCommentEvent": {
|
||||||
if (!["created"].includes(payload.action))
|
if (!["created"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {pull_request:{user:{login:user}, title, number}, comment:{body:content, performed_via_github_app:mobile}} = payload
|
const {pull_request: {user: {login: user}, title, number}, comment: {body: content, performed_via_github_app: mobile}} = payload
|
||||||
if (ignored.includes(user))
|
if (ignored.includes(user))
|
||||||
return null
|
return null
|
||||||
return {type:"comment", on:"pr", actor, timestamp, repo, content:await imports.markdown(content, {mode:markdown, codelines}), user, mobile, number, title}
|
return {type: "comment", on: "pr", actor, timestamp, repo, content: await imports.markdown(content, {mode: markdown, codelines}), user, mobile, number, title}
|
||||||
}
|
}
|
||||||
//Pushed commits
|
//Pushed commits
|
||||||
case "PushEvent": {
|
case "PushEvent": {
|
||||||
let {size, commits, ref} = payload
|
let {size, commits, ref} = payload
|
||||||
commits = commits.filter(({author:{email}}) => !ignored.includes(email))
|
commits = commits.filter(({author: {email}}) => !ignored.includes(email))
|
||||||
if (!commits.length)
|
if (!commits.length)
|
||||||
return null
|
return null
|
||||||
if (commits.slice(-1).pop()?.message.startsWith("Merge branch "))
|
if (commits.slice(-1).pop()?.message.startsWith("Merge branch "))
|
||||||
commits = commits.slice(-1)
|
commits = commits.slice(-1)
|
||||||
return {type:"push", actor, timestamp, repo, size, branch:ref.match(/refs.heads.(?<branch>.*)/)?.groups?.branch ?? null, commits:commits.reverse().map(({sha, message}) => ({sha:sha.substring(0, 7), message}))}
|
return {type: "push", actor, timestamp, repo, size, branch: ref.match(/refs.heads.(?<branch>.*)/)?.groups?.branch ?? null, commits: commits.reverse().map(({sha, message}) => ({sha: sha.substring(0, 7), message}))}
|
||||||
}
|
}
|
||||||
//Released
|
//Released
|
||||||
case "ReleaseEvent": {
|
case "ReleaseEvent": {
|
||||||
if (!["published"].includes(payload.action))
|
if (!["published"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {action, release:{name, tag_name, prerelease, draft, body:content}} = payload
|
const {action, release: {name, tag_name, prerelease, draft, body: content}} = payload
|
||||||
return {type:"release", actor, timestamp, repo, action, name:name || tag_name, prerelease, draft, content:await imports.markdown(content, {mode:markdown, codelines})}
|
return {type: "release", actor, timestamp, repo, action, name: name || tag_name, prerelease, draft, content: await imports.markdown(content, {mode: markdown, codelines})}
|
||||||
}
|
}
|
||||||
//Starred a repository
|
//Starred a repository
|
||||||
case "WatchEvent": {
|
case "WatchEvent": {
|
||||||
if (!["started"].includes(payload.action))
|
if (!["started"].includes(payload.action))
|
||||||
return null
|
return null
|
||||||
const {action} = payload
|
const {action} = payload
|
||||||
return {type:"star", actor, timestamp, repo, action}
|
return {type: "star", actor, timestamp, repo, action}
|
||||||
}
|
}
|
||||||
//Unknown event
|
//Unknown event
|
||||||
default: {
|
default: {
|
||||||
@@ -170,10 +170,10 @@ export default async function({login, data, rest, q, account, imports}, {enabled
|
|||||||
.slice(0, limit)
|
.slice(0, limit)
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {timestamps, events:activity}
|
return {timestamps, events: activity}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ export default async function({login, data, queries, imports, q, account}, {enab
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {limit, "limit.characters":limit_characters, medias, sections, shuffle, user} = imports.metadata.plugins.anilist.inputs({data, account, q})
|
let {limit, "limit.characters": limit_characters, medias, sections, shuffle, user} = imports.metadata.plugins.anilist.inputs({data, account, q})
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
const result = {user:{name:user, stats:null, genres:[]}, lists:Object.fromEntries(medias.map(type => [type, {}])), characters:[], sections}
|
const result = {user: {name: user, stats: null, genres: []}, lists: Object.fromEntries(medias.map(type => [type, {}])), characters: [], sections}
|
||||||
|
|
||||||
//User statistics
|
//User statistics
|
||||||
for (let retried = false; !retried; retried = true) {
|
for (let retried = false; !retried; retried = true) {
|
||||||
try {
|
try {
|
||||||
//Query API
|
//Query API
|
||||||
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (user statistics)`)
|
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (user statistics)`)
|
||||||
const {data:{data:{User:{statistics:stats}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user}, query:queries.anilist.statistics()})
|
const {data: {data: {User: {statistics: stats}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user}, query: queries.anilist.statistics()})
|
||||||
//Format and save results
|
//Format and save results
|
||||||
result.user.stats = stats
|
result.user.stats = stats
|
||||||
result.user.genres = [...new Set([...stats.anime.genres.map(({genre}) => genre), ...stats.manga.genres.map(({genre}) => genre)])]
|
result.user.genres = [...new Set([...stats.anime.genres.map(({genre}) => genre), ...stats.manga.genres.map(({genre}) => genre)])]
|
||||||
@@ -34,7 +34,7 @@ export default async function({login, data, queries, imports, q, account}, {enab
|
|||||||
try {
|
try {
|
||||||
//Query API
|
//Query API
|
||||||
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (medias lists - ${type})`)
|
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (medias lists - ${type})`)
|
||||||
const {data:{data:{MediaListCollection:{lists}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, type:type.toLocaleUpperCase()}, query:queries.anilist.medias()})
|
const {data: {data: {MediaListCollection: {lists}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user, type: type.toLocaleUpperCase()}, query: queries.anilist.medias()})
|
||||||
//Format and save results
|
//Format and save results
|
||||||
for (const {name, entries} of lists) {
|
for (const {name, entries} of lists) {
|
||||||
//Format results
|
//Format results
|
||||||
@@ -65,10 +65,10 @@ export default async function({login, data, queries, imports, q, account}, {enab
|
|||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites ${type}s - page ${page})`)
|
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites ${type}s - page ${page})`)
|
||||||
const {data:{data:{User:{favourites:{[type]:{nodes, pageInfo:cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, page}, query:queries.anilist.favorites({type})})
|
const {data: {data: {User: {favourites: {[type]: {nodes, pageInfo: cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user, page}, query: queries.anilist.favorites({type})})
|
||||||
page++
|
page++
|
||||||
next = cursor.hasNextPage
|
next = cursor.hasNextPage
|
||||||
list.push(...await Promise.all(nodes.map(media => format({media:{progess:null, score:null, media}, imports}))))
|
list.push(...await Promise.all(nodes.map(media => format({media: {progess: null, score: null, media}, imports}))))
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (await retry({login, imports, error}))
|
if (await retry({login, imports, error}))
|
||||||
@@ -95,12 +95,12 @@ export default async function({login, data, queries, imports, q, account}, {enab
|
|||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites characters - page ${page})`)
|
console.debug(`metrics/compute/${login}/plugins > anilist > querying api (favorites characters - page ${page})`)
|
||||||
const {data:{data:{User:{favourites:{characters:{nodes, pageInfo:cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables:{name:user, page}, query:queries.anilist.characters()})
|
const {data: {data: {User: {favourites: {characters: {nodes, pageInfo: cursor}}}}}} = await imports.axios.post("https://graphql.anilist.co", {variables: {name: user, page}, query: queries.anilist.characters()})
|
||||||
page++
|
page++
|
||||||
next = cursor.hasNextPage
|
next = cursor.hasNextPage
|
||||||
for (const {name:{full:name}, image:{medium:artwork}} of nodes) {
|
for (const {name: {full: name}, image: {medium: artwork}} of nodes) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > anilist > processing ${name}`)
|
console.debug(`metrics/compute/${login}/plugins > anilist > processing ${name}`)
|
||||||
characters.push({name, artwork:await imports.imgb64(artwork)})
|
characters.push({name, artwork: await imports.imgb64(artwork)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -129,24 +129,24 @@ export default async function({login, data, queries, imports, q, account}, {enab
|
|||||||
message = `API returned ${status}`
|
message = `API returned ${status}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
}
|
}
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Media formatter */
|
/**Media formatter */
|
||||||
async function format({media, imports}) {
|
async function format({media, imports}) {
|
||||||
const {progress, score:userScore, media:{title, description, status, startDate:{year:release}, genres, averageScore, episodes, chapters, type, coverImage:{medium:artwork}}} = media
|
const {progress, score: userScore, media: {title, description, status, startDate: {year: release}, genres, averageScore, episodes, chapters, type, coverImage: {medium: artwork}}} = media
|
||||||
return {
|
return {
|
||||||
name:title.romaji,
|
name: title.romaji,
|
||||||
type,
|
type,
|
||||||
status,
|
status,
|
||||||
release,
|
release,
|
||||||
genres,
|
genres,
|
||||||
progress,
|
progress,
|
||||||
description:description.replace(/<br\s*\\?>/g, " "),
|
description: description.replace(/<br\s*\\?>/g, " "),
|
||||||
scores:{user:userScore, community:averageScore},
|
scores: {user: userScore, community: averageScore},
|
||||||
released:type === "ANIME" ? episodes : chapters,
|
released: type === "ANIME" ? episodes : chapters,
|
||||||
artwork:await imports.imgb64(artwork),
|
artwork: await imports.imgb64(artwork),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
export default async function({login, graphql, rest, data, q, queries, imports}, conf) {
|
export default async function({login, graphql, rest, data, q, queries, imports}, conf) {
|
||||||
//Load inputs
|
//Load inputs
|
||||||
console.debug(`metrics/compute/${login}/base > started`)
|
console.debug(`metrics/compute/${login}/base > started`)
|
||||||
let {indepth, "repositories.forks":_forks, "repositories.affiliations":_affiliations, "repositories.batch":_batch} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"})
|
let {indepth, "repositories.forks": _forks, "repositories.affiliations": _affiliations, "repositories.batch": _batch} = imports.metadata.plugins.base.inputs({data, q, account: "bypass"})
|
||||||
const extras = conf.settings.extras?.features ?? conf.settings.extras?.default
|
const extras = conf.settings.extras?.features ?? conf.settings.extras?.default
|
||||||
const repositories = conf.settings.repositories || 100
|
const repositories = conf.settings.repositories || 100
|
||||||
const forks = _forks ? "" : ", isFork: false"
|
const forks = _forks ? "" : ", isFork: false"
|
||||||
@@ -29,18 +29,18 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
//Query data from GitHub API
|
//Query data from GitHub API
|
||||||
console.debug(`metrics/compute/${login}/base > account ${account}`)
|
console.debug(`metrics/compute/${login}/base > account ${account}`)
|
||||||
const queried = await graphql(queries.base[account]({login}))
|
const queried = await graphql(queries.base[account]({login}))
|
||||||
Object.assign(data, {user:queried[account]})
|
Object.assign(data, {user: queried[account]})
|
||||||
postprocess?.[account]({login, data})
|
postprocess?.[account]({login, data})
|
||||||
try {
|
try {
|
||||||
Object.assign(data.user, (await graphql(queries.base[`${account}.x`]({login, account, "calendar.from":new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to":(new Date()).toISOString()})))[account])
|
Object.assign(data.user, (await graphql(queries.base[`${account}.x`]({login, account, "calendar.from": new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to": (new Date()).toISOString()})))[account])
|
||||||
console.debug(`metrics/compute/${login}/base > successfully loaded bulk query`)
|
console.debug(`metrics/compute/${login}/base > successfully loaded bulk query`)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
console.debug(`metrics/compute/${login}/base > failed to load bulk query, falling back to unit queries`)
|
console.debug(`metrics/compute/${login}/base > failed to load bulk query, falling back to unit queries`)
|
||||||
//Query basic fields
|
//Query basic fields
|
||||||
const fields = {
|
const fields = {
|
||||||
user:["packages", "starredRepositories", "watching", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "followers", "following", "issueComments", "organizations", "repositoriesContributedTo(includeUserRepositories: true)"],
|
user: ["packages", "starredRepositories", "watching", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "followers", "following", "issueComments", "organizations", "repositoriesContributedTo(includeUserRepositories: true)"],
|
||||||
organization:["packages", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "membersWithRole"],
|
organization: ["packages", "sponsorshipsAsSponsor", "sponsorshipsAsMaintainer", "membersWithRole"],
|
||||||
}[account] ?? []
|
}[account] ?? []
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
try {
|
try {
|
||||||
@@ -48,7 +48,7 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
console.debug(`metrics/compute/${login}/base > failed to retrieve ${field}`)
|
console.debug(`metrics/compute/${login}/base > failed to retrieve ${field}`)
|
||||||
data.user[field] = {totalCount:NaN}
|
data.user[field] = {totalCount: NaN}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Query repositories fields
|
//Query repositories fields
|
||||||
@@ -68,7 +68,7 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"]
|
const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"]
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
try {
|
try {
|
||||||
Object.assign(data.user.contributionsCollection, (await graphql(queries.base.contributions({login, account, field, range:""})))[account].contributionsCollection)
|
Object.assign(data.user.contributionsCollection, (await graphql(queries.base.contributions({login, account, field, range: ""})))[account].contributionsCollection)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
console.debug(`metrics/compute/${login}/base > failed to retrieve contributionsCollection.${field}`)
|
console.debug(`metrics/compute/${login}/base > failed to retrieve contributionsCollection.${field}`)
|
||||||
@@ -78,17 +78,17 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
}
|
}
|
||||||
//Query calendar
|
//Query calendar
|
||||||
try {
|
try {
|
||||||
Object.assign(data.user, (await graphql(queries.base.calendar({login, "calendar.from":new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to":(new Date()).toISOString()})))[account])
|
Object.assign(data.user, (await graphql(queries.base.calendar({login, "calendar.from": new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(), "calendar.to": (new Date()).toISOString()})))[account])
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
console.debug(`metrics/compute/${login}/base > failed to retrieve contributions calendar`)
|
console.debug(`metrics/compute/${login}/base > failed to retrieve contributions calendar`)
|
||||||
data.user.calendar = {contributionCalendar:{weeks:[]}}
|
data.user.calendar = {contributionCalendar: {weeks: []}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Query contributions collection over account lifetime instead of last year
|
//Query contributions collection over account lifetime instead of last year
|
||||||
if (account === "user") {
|
if (account === "user") {
|
||||||
if ((indepth)&&(extras)) {
|
if ((indepth) && (extras)) {
|
||||||
const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"]
|
const fields = ["totalRepositoriesWithContributedCommits", "totalCommitContributions", "restrictedContributionsCount", "totalIssueContributions", "totalPullRequestContributions", "totalPullRequestReviewContributions"]
|
||||||
const start = new Date(data.user.createdAt)
|
const start = new Date(data.user.createdAt)
|
||||||
const end = new Date()
|
const end = new Date()
|
||||||
@@ -111,7 +111,7 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
//Fetch data from api
|
//Fetch data from api
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > base > loading contributions collections for ${field} from "${from.toISOString()}" to "${dto.toISOString()}"`)
|
console.debug(`metrics/compute/${login}/plugins > base > loading contributions collections for ${field} from "${from.toISOString()}" to "${dto.toISOString()}"`)
|
||||||
const {[account]:{contributionsCollection}} = await graphql(queries.base.contributions({login, account, field, range:`(from: "${from.toISOString()}", to: "${dto.toISOString()}")`}))
|
const {[account]: {contributionsCollection}} = await graphql(queries.base.contributions({login, account, field, range: `(from: "${from.toISOString()}", to: "${dto.toISOString()}")`}))
|
||||||
collection[field] += contributionsCollection[field]
|
collection[field] += contributionsCollection[field]
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -127,7 +127,7 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/base > loading user commits history`)
|
console.debug(`metrics/compute/${login}/base > loading user commits history`)
|
||||||
const {data:{total_count:total = 0}} = await rest.search.commits({q:`author:${login}`})
|
const {data: {total_count: total = 0}} = await rest.search.commits({q: `author:${login}`})
|
||||||
data.user.contributionsCollection.totalCommitContributions = Math.max(total, data.user.contributionsCollection.totalCommitContributions)
|
data.user.contributionsCollection.totalCommitContributions = Math.max(total, data.user.contributionsCollection.totalCommitContributions)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -136,18 +136,18 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Query repositories from GitHub API
|
//Query repositories from GitHub API
|
||||||
for (const type of ({user:["repositories", "repositoriesContributedTo"], organization:["repositories"]}[account] ?? [])) {
|
for (const type of ({user: ["repositories", "repositoriesContributedTo"], organization: ["repositories"]}[account] ?? [])) {
|
||||||
//Iterate through repositories
|
//Iterate through repositories
|
||||||
let cursor = null
|
let cursor = null
|
||||||
let pushed = 0
|
let pushed = 0
|
||||||
const options = {repositories:{forks, affiliations, constraints:""}, repositoriesContributedTo:{forks:"", affiliations:"", constraints:", includeUserRepositories: false, contributionTypes: COMMIT"}}[type] ?? null
|
const options = {repositories: {forks, affiliations, constraints: ""}, repositoriesContributedTo: {forks: "", affiliations: "", constraints: ", includeUserRepositories: false, contributionTypes: COMMIT"}}[type] ?? null
|
||||||
data.user[type] = data.user[type] ?? {}
|
data.user[type] = data.user[type] ?? {}
|
||||||
data.user[type].nodes = data.user[type].nodes ?? []
|
data.user[type].nodes = data.user[type].nodes ?? []
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/base > retrieving ${type} after ${cursor}`)
|
console.debug(`metrics/compute/${login}/base > retrieving ${type} after ${cursor}`)
|
||||||
const request = {}
|
const request = {}
|
||||||
try {
|
try {
|
||||||
Object.assign(request, await graphql(queries.base.repositories({login, account, type, after:cursor ? `after: "${cursor}"` : "", repositories:Math.min(repositories, {user:_batch, organization:Math.min(25, _batch)}[account]), ...options})))
|
Object.assign(request, await graphql(queries.base.repositories({login, account, type, after: cursor ? `after: "${cursor}"` : "", repositories: Math.min(repositories, {user: _batch, organization: Math.min(25, _batch)}[account]), ...options})))
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.debug(`metrics/compute/${login}/base > failed to retrieve ${_batch} repositories after ${cursor}, this is probably due to an API timeout, halving batch`)
|
console.debug(`metrics/compute/${login}/base > failed to retrieve ${_batch} repositories after ${cursor}, this is probably due to an API timeout, halving batch`)
|
||||||
@@ -158,7 +158,7 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const {[account]:{[type]:{edges = [], nodes = []} = {}}} = request
|
const {[account]: {[type]: {edges = [], nodes = []} = {}}} = request
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
data.user[type].nodes.push(...nodes)
|
data.user[type].nodes.push(...nodes)
|
||||||
pushed = nodes.length
|
pushed = nodes.length
|
||||||
@@ -176,7 +176,7 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
//Fetch missing packages count from ghcr.io using REST API (as GraphQL API does not support it yet)
|
//Fetch missing packages count from ghcr.io using REST API (as GraphQL API does not support it yet)
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/base > patching packages count if possible`)
|
console.debug(`metrics/compute/${login}/base > patching packages count if possible`)
|
||||||
const {data:packages} = await rest.packages[{user:"listPackagesForUser", organization:"listPackagesForOrganization"}[account]]({package_type:"container", org:login, username:login})
|
const {data: packages} = await rest.packages[{user: "listPackagesForUser", organization: "listPackagesForOrganization"}[account]]({package_type: "container", org: login, username: login})
|
||||||
data.user.packages.totalCount += packages.length
|
data.user.packages.totalCount += packages.length
|
||||||
console.debug(`metrics/compute/${login}/base > patched packages count (added ${packages.length} from ghcr.io)`)
|
console.debug(`metrics/compute/${login}/base > patched packages count (added ${packages.length} from ghcr.io)`)
|
||||||
}
|
}
|
||||||
@@ -184,8 +184,8 @@ export default async function({login, graphql, rest, data, q, queries, imports},
|
|||||||
console.debug(`metrics/compute/${login}/base > failed to patch packages count, maybe read:packages scope was not provided`)
|
console.debug(`metrics/compute/${login}/base > failed to patch packages count, maybe read:packages scope was not provided`)
|
||||||
}
|
}
|
||||||
//Shared options
|
//Shared options
|
||||||
let {"repositories.skipped":skipped, "users.ignored":ignored, "commits.authoring":authoring} = imports.metadata.plugins.base.inputs({data, q, account:"bypass"})
|
let {"repositories.skipped": skipped, "users.ignored": ignored, "commits.authoring": authoring} = imports.metadata.plugins.base.inputs({data, q, account: "bypass"})
|
||||||
data.shared = {"repositories.skipped":skipped, "users.ignored":ignored, "commits.authoring":authoring, "repositories.batch":_batch}
|
data.shared = {"repositories.skipped": skipped, "users.ignored": ignored, "commits.authoring": authoring, "repositories.batch": _batch}
|
||||||
console.debug(`metrics/compute/${login}/base > shared options > ${JSON.stringify(data.shared)}`)
|
console.debug(`metrics/compute/${login}/base > shared options > ${JSON.stringify(data.shared)}`)
|
||||||
//Success
|
//Success
|
||||||
console.debug(`metrics/compute/${login}/base > graphql query > account ${account} > success`)
|
console.debug(`metrics/compute/${login}/base > graphql query > account ${account} > success`)
|
||||||
@@ -213,9 +213,9 @@ const postprocess = {
|
|||||||
console.debug(`metrics/compute/${login}/base > applying postprocessing`)
|
console.debug(`metrics/compute/${login}/base > applying postprocessing`)
|
||||||
data.account = "user"
|
data.account = "user"
|
||||||
Object.assign(data.user, {
|
Object.assign(data.user, {
|
||||||
isVerified:false,
|
isVerified: false,
|
||||||
repositories:{},
|
repositories: {},
|
||||||
contributionsCollection:{},
|
contributionsCollection: {},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
//Organization
|
//Organization
|
||||||
@@ -223,44 +223,44 @@ const postprocess = {
|
|||||||
console.debug(`metrics/compute/${login}/base > applying postprocessing`)
|
console.debug(`metrics/compute/${login}/base > applying postprocessing`)
|
||||||
data.account = "organization"
|
data.account = "organization"
|
||||||
Object.assign(data.user, {
|
Object.assign(data.user, {
|
||||||
isHireable:false,
|
isHireable: false,
|
||||||
repositories:{},
|
repositories: {},
|
||||||
starredRepositories:{totalCount:NaN},
|
starredRepositories: {totalCount: NaN},
|
||||||
watching:{totalCount:NaN},
|
watching: {totalCount: NaN},
|
||||||
contributionsCollection:{
|
contributionsCollection: {
|
||||||
totalRepositoriesWithContributedCommits:NaN,
|
totalRepositoriesWithContributedCommits: NaN,
|
||||||
totalCommitContributions:NaN,
|
totalCommitContributions: NaN,
|
||||||
restrictedContributionsCount:NaN,
|
restrictedContributionsCount: NaN,
|
||||||
totalIssueContributions:NaN,
|
totalIssueContributions: NaN,
|
||||||
totalPullRequestContributions:NaN,
|
totalPullRequestContributions: NaN,
|
||||||
totalPullRequestReviewContributions:NaN,
|
totalPullRequestReviewContributions: NaN,
|
||||||
},
|
},
|
||||||
calendar:{contributionCalendar:{weeks:[]}},
|
calendar: {contributionCalendar: {weeks: []}},
|
||||||
repositoriesContributedTo:{totalCount:NaN, nodes:[]},
|
repositoriesContributedTo: {totalCount: NaN, nodes: []},
|
||||||
followers:{totalCount:NaN},
|
followers: {totalCount: NaN},
|
||||||
following:{totalCount:NaN},
|
following: {totalCount: NaN},
|
||||||
issueComments:{totalCount:NaN},
|
issueComments: {totalCount: NaN},
|
||||||
organizations:{totalCount:NaN},
|
organizations: {totalCount: NaN},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
//Skip base content query and instantiate an empty user instance
|
//Skip base content query and instantiate an empty user instance
|
||||||
skip({login, data, imports}) {
|
skip({login, data, imports}) {
|
||||||
data.user = {}
|
data.user = {}
|
||||||
data.shared = imports.metadata.plugins.base.inputs({data, q:{}, account:"bypass"})
|
data.shared = imports.metadata.plugins.base.inputs({data, q: {}, account: "bypass"})
|
||||||
for (const account of ["user", "organization"])
|
for (const account of ["user", "organization"])
|
||||||
postprocess?.[account]({login, data})
|
postprocess?.[account]({login, data})
|
||||||
data.account = "bypass"
|
data.account = "bypass"
|
||||||
Object.assign(data.user, {
|
Object.assign(data.user, {
|
||||||
databaseId:NaN,
|
databaseId: NaN,
|
||||||
name:login,
|
name: login,
|
||||||
login,
|
login,
|
||||||
createdAt:new Date(),
|
createdAt: new Date(),
|
||||||
avatarUrl:`https://github.com/${login}.png`,
|
avatarUrl: `https://github.com/${login}.png`,
|
||||||
websiteUrl:null,
|
websiteUrl: null,
|
||||||
twitterUsername:login,
|
twitterUsername: login,
|
||||||
repositories:{totalCount:NaN, totalDiskUsage:NaN, nodes:[]},
|
repositories: {totalCount: NaN, totalDiskUsage: NaN, nodes: []},
|
||||||
packages:{totalCount:NaN},
|
packages: {totalCount: NaN},
|
||||||
repositoriesContributedTo:{totalCount:NaN, nodes:[]},
|
repositoriesContributedTo: {totalCount: NaN, nodes: []},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ export default async function({login, q, imports, data, rest, account}, {enabled
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
let context = {mode:"user"}
|
let context = {mode: "user"}
|
||||||
if (q.repo) {
|
if (q.repo) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > code > switched to repository mode`)
|
console.debug(`metrics/compute/${login}/plugins > code > switched to repository mode`)
|
||||||
const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift()
|
const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift()
|
||||||
context = {...context, mode:"repository", owner, repo}
|
context = {...context, mode: "repository", owner, repo}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
@@ -31,14 +31,14 @@ export default async function({login, q, imports, data, rest, account}, {enabled
|
|||||||
...[
|
...[
|
||||||
...await Promise.all([
|
...await Promise.all([
|
||||||
...(context.mode === "repository"
|
...(context.mode === "repository"
|
||||||
? await rest.activity.listRepoEvents({owner:context.owner, repo:context.repo})
|
? await rest.activity.listRepoEvents({owner: context.owner, repo: context.repo})
|
||||||
: await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data
|
: await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data
|
||||||
.filter(({type}) => type === "PushEvent")
|
.filter(({type}) => type === "PushEvent")
|
||||||
.filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase())
|
.filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase())
|
||||||
.filter(({repo:{name:repo}}) => !((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo))))
|
.filter(({repo: {name: repo}}) => !((skipped.includes(repo.split("/").pop())) || (skipped.includes(repo))))
|
||||||
.filter(event => visibility === "public" ? event.public : true)
|
.filter(event => visibility === "public" ? event.public : true)
|
||||||
.filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true)
|
.filter(({created_at}) => Number.isFinite(days) ? new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true)
|
||||||
.flatMap(({created_at:created, payload}) => Promise.all(payload.commits.map(async commit => ({created:new Date(created), ...(await rest.request(commit.url)).data})))),
|
.flatMap(({created_at: created, payload}) => Promise.all(payload.commits.map(async commit => ({created: new Date(created), ...(await rest.request(commit.url)).data})))),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
.flat()
|
.flat()
|
||||||
@@ -54,11 +54,10 @@ export default async function({login, q, imports, data, rest, account}, {enabled
|
|||||||
|
|
||||||
//Search for a random snippet
|
//Search for a random snippet
|
||||||
let files = events
|
let files = events
|
||||||
.flatMap(({created, sha, commit:{message, url}, files}) => files.map(({filename, status, additions, deletions, patch}) => ({created, sha, message, filename, status, additions, deletions, patch, repo:url.match(/repos[/](?<repo>[\s\S]+)[/]git[/]commits/)?.groups?.repo}))
|
.flatMap(({created, sha, commit: {message, url}, files}) => files.map(({filename, status, additions, deletions, patch}) => ({created, sha, message, filename, status, additions, deletions, patch, repo: url.match(/repos[/](?<repo>[\s\S]+)[/]git[/]commits/)?.groups?.repo})))
|
||||||
)
|
|
||||||
.filter(({patch}) => (patch ? (patch.match(/\n/mg)?.length ?? 1) : Infinity) < lines)
|
.filter(({patch}) => (patch ? (patch.match(/\n/mg)?.length ?? 1) : Infinity) < lines)
|
||||||
for (const file of files)
|
for (const file of files)
|
||||||
file.language = await imports.language({...file, prefix:login}).catch(() => "unknown")
|
file.language = await imports.language({...file, prefix: login}).catch(() => "unknown")
|
||||||
files = files.filter(({language}) => (!languages.length) || (languages.includes(language.toLocaleLowerCase())))
|
files = files.filter(({language}) => (!languages.length) || (languages.includes(language.toLocaleLowerCase())))
|
||||||
const snippet = files[Math.floor(Math.random() * files.length)] ?? null
|
const snippet = files[Math.floor(Math.random() * files.length)] ?? null
|
||||||
if (snippet) {
|
if (snippet) {
|
||||||
@@ -80,6 +79,6 @@ export default async function({login, q, imports, data, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,19 +11,19 @@ export default async function({q, data, imports, account}, {enabled = false} = {
|
|||||||
|
|
||||||
//Fortunes list
|
//Fortunes list
|
||||||
const fortunes = [
|
const fortunes = [
|
||||||
{chance:.06, color:"#F51C6A", text:"Reply hazy"},
|
{chance: .06, color: "#F51C6A", text: "Reply hazy"},
|
||||||
{chance:.03, color:"#FD4D32", text:"Excellent Luck"},
|
{chance: .03, color: "#FD4D32", text: "Excellent Luck"},
|
||||||
{chance:.16, color:"#E7890C", text:"Good Luck"},
|
{chance: .16, color: "#E7890C", text: "Good Luck"},
|
||||||
{chance:.24, color:"#BAC200", text:"Average Luck"},
|
{chance: .24, color: "#BAC200", text: "Average Luck"},
|
||||||
{chance:.16, color:"#7FEC11", text:"Bad Luck"},
|
{chance: .16, color: "#7FEC11", text: "Bad Luck"},
|
||||||
{chance:.06, color:"#43FD3B", text:"Good news will come to you by mail"},
|
{chance: .06, color: "#43FD3B", text: "Good news will come to you by mail"},
|
||||||
{chance:.06, color:"#16F174", text:"( ´_ゝ`)フーン "},
|
{chance: .06, color: "#16F174", text: "( ´_ゝ`)フーン "},
|
||||||
{chance:.06, color:"#00CBB0", text:"キタ━━━━━━(゚∀゚)━━━━━━ !!!!"},
|
{chance: .06, color: "#00CBB0", text: "キタ━━━━━━(゚∀゚)━━━━━━ !!!!"},
|
||||||
{chance:.06, color:"#0893E1", text:"You will meet a dark handsome stranger"},
|
{chance: .06, color: "#0893E1", text: "You will meet a dark handsome stranger"},
|
||||||
{chance:.06, color:"#2A56FB", text:"Better not tell you now"},
|
{chance: .06, color: "#2A56FB", text: "Better not tell you now"},
|
||||||
{chance:.06, color:"#6023F8", text:"Outlook good"},
|
{chance: .06, color: "#6023F8", text: "Outlook good"},
|
||||||
{chance:.04, color:"#9D05DA", text:"Very Bad Luck"},
|
{chance: .04, color: "#9D05DA", text: "Very Bad Luck"},
|
||||||
{chance:.01, color:"#D302A7", text:"Godly Luck"},
|
{chance: .01, color: "#D302A7", text: "Godly Luck"},
|
||||||
]
|
]
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
@@ -40,6 +40,6 @@ export default async function({q, data, imports, account}, {enabled = false} = {
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function({q, imports, data, account}, {enabled = false} = {
|
|||||||
let {url, datapoints, lowalert, highalert, urgentlowalert, urgenthighalert} = imports.metadata.plugins.nightscout.inputs({data, account, q})
|
let {url, datapoints, lowalert, highalert, urgentlowalert, urgenthighalert} = imports.metadata.plugins.nightscout.inputs({data, account, q})
|
||||||
|
|
||||||
if (!url || url === "https://example.herokuapp.com")
|
if (!url || url === "https://example.herokuapp.com")
|
||||||
throw {error:{message:"Nightscout site URL isn't set!"}}
|
throw {error: {message: "Nightscout site URL isn't set!"}}
|
||||||
if (url.substring(url.length - 1) !== "/")
|
if (url.substring(url.length - 1) !== "/")
|
||||||
url += "/"
|
url += "/"
|
||||||
if (url.substring(0, 7) === "http://")
|
if (url.substring(0, 7) === "http://")
|
||||||
@@ -44,13 +44,13 @@ export default async function({q, imports, data, account}, {enabled = false} = {
|
|||||||
resp.data[i].color = color
|
resp.data[i].color = color
|
||||||
resp.data[i].alert = alertName
|
resp.data[i].alert = alertName
|
||||||
}
|
}
|
||||||
return {data:resp.data.reverse()}
|
return {data: resp.data.reverse()}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ export default async function({q, imports, data, account}, {enabled = false, tok
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
return {poops:[], days:7}
|
return {poops: [], days: 7}
|
||||||
|
|
||||||
const {days} = imports.metadata.plugins.poopmap.inputs({data, account, q})
|
const {days} = imports.metadata.plugins.poopmap.inputs({data, account, q})
|
||||||
const {data:{poops}} = await imports.axios.get(`https://api.poopmap.net/api/v1/public_links/${token}`)
|
const {data: {poops}} = await imports.axios.get(`https://api.poopmap.net/api/v1/public_links/${token}`)
|
||||||
|
|
||||||
const filteredPoops = poops.filter(poop => {
|
const filteredPoops = poops.filter(poop => {
|
||||||
const createdAt = new Date(poop.created_at)
|
const createdAt = new Date(poop.created_at)
|
||||||
@@ -18,7 +18,7 @@ export default async function({q, imports, data, account}, {enabled = false, tok
|
|||||||
return createdAt > new Date().getTime() - days * 24 * 60 * 60 * 1000
|
return createdAt > new Date().getTime() - days * 24 * 60 * 60 * 1000
|
||||||
})
|
})
|
||||||
|
|
||||||
const hours = {max:0}
|
const hours = {max: 0}
|
||||||
for (let i = 0; i < filteredPoops.length; i++) {
|
for (let i = 0; i < filteredPoops.length; i++) {
|
||||||
const poop = filteredPoops[i]
|
const poop = filteredPoops[i]
|
||||||
const hour = new Date(poop.created_at).getHours()
|
const hour = new Date(poop.created_at).getHours()
|
||||||
@@ -28,10 +28,10 @@ export default async function({q, imports, data, account}, {enabled = false, tok
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {poops:hours, days}
|
return {poops: hours, days}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
//Load inputs
|
//Load inputs
|
||||||
let {url, selector, title, background} = imports.metadata.plugins.screenshot.inputs({data, account, q})
|
let {url, selector, title, background} = imports.metadata.plugins.screenshot.inputs({data, account, q})
|
||||||
if (!url)
|
if (!url)
|
||||||
throw {error:{message:"An url is required"}}
|
throw {error: {message: "An url is required"}}
|
||||||
|
|
||||||
//Start puppeteer and navigate to page
|
//Start puppeteer and navigate to page
|
||||||
console.debug(`metrics/compute/${login}/plugins > screenshot > starting browser`)
|
console.debug(`metrics/compute/${login}/plugins > screenshot > starting browser`)
|
||||||
const browser = await imports.puppeteer.launch()
|
const browser = await imports.puppeteer.launch()
|
||||||
console.debug(`metrics/compute/${login}/plugins > screenshot > started ${await browser.version()}`)
|
console.debug(`metrics/compute/${login}/plugins > screenshot > started ${await browser.version()}`)
|
||||||
const page = await browser.newPage()
|
const page = await browser.newPage()
|
||||||
await page.setViewport({width:1280, height:1280})
|
await page.setViewport({width: 1280, height: 1280})
|
||||||
console.debug(`metrics/compute/${login}/plugins > screenshot > loading ${url}`)
|
console.debug(`metrics/compute/${login}/plugins > screenshot > loading ${url}`)
|
||||||
await page.goto(url)
|
await page.goto(url)
|
||||||
|
|
||||||
@@ -27,17 +27,17 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
return {x, y, width, height}
|
return {x, y, width, height}
|
||||||
}, selector)
|
}, selector)
|
||||||
console.debug(`metrics/compute/${login}/plugins > screenshot > coordinates ${JSON.stringify(clip)}`)
|
console.debug(`metrics/compute/${login}/plugins > screenshot > coordinates ${JSON.stringify(clip)}`)
|
||||||
const [buffer] = await imports.record({page, ...clip, frames:1, background})
|
const [buffer] = await imports.record({page, ...clip, frames: 1, background})
|
||||||
const screenshot = await (await imports.jimp.read(Buffer.from(buffer.split(",").pop(), "base64"))).resize(Math.min(454 * (1 + data.large), clip.width), imports.jimp.AUTO)
|
const screenshot = await (await imports.jimp.read(Buffer.from(buffer.split(",").pop(), "base64"))).resize(Math.min(454 * (1 + data.large), clip.width), imports.jimp.AUTO)
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {image:await screenshot.getBase64Async("image/png"), title, height:screenshot.bitmap.height, width:screenshot.bitmap.width, url}
|
return {image: await screenshot.getBase64Async("image/png"), title, height: screenshot.bitmap.height, width: screenshot.bitmap.width, url}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {title:"Screenshot error", error:{message:"An error occured", instance:error}}
|
throw {title: "Screenshot error", error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,42 +9,42 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
//Load inputs
|
//Load inputs
|
||||||
let {symbol, interval, duration} = imports.metadata.plugins.stock.inputs({data, account, q})
|
let {symbol, interval, duration} = imports.metadata.plugins.stock.inputs({data, account, q})
|
||||||
if (!token)
|
if (!token)
|
||||||
throw {error:{message:"A token is required"}}
|
throw {error: {message: "A token is required"}}
|
||||||
if (!symbol)
|
if (!symbol)
|
||||||
throw {error:{message:"A company stock symbol is required"}}
|
throw {error: {message: "A company stock symbol is required"}}
|
||||||
symbol = symbol.toLocaleUpperCase()
|
symbol = symbol.toLocaleUpperCase()
|
||||||
|
|
||||||
//Query API for company informations
|
//Query API for company informations
|
||||||
console.debug(`metrics/compute/${login}/plugins > stock > querying api for company`)
|
console.debug(`metrics/compute/${login}/plugins > stock > querying api for company`)
|
||||||
const {data:{quoteType:{shortName:company}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-profile", {
|
const {data: {quoteType: {shortName: company}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-profile", {
|
||||||
params:{symbol, region:"US"},
|
params: {symbol, region: "US"},
|
||||||
headers:{"x-rapidapi-key":token},
|
headers: {"x-rapidapi-key": token},
|
||||||
})
|
})
|
||||||
|
|
||||||
//Query API for sotck charts
|
//Query API for sotck charts
|
||||||
console.debug(`metrics/compute/${login}/plugins > stock > querying api for stock`)
|
console.debug(`metrics/compute/${login}/plugins > stock > querying api for stock`)
|
||||||
const {data:{chart:{result:[{meta, timestamp, indicators:{quote:[{close}]}}]}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-chart", {
|
const {data: {chart: {result: [{meta, timestamp, indicators: {quote: [{close}]}}]}}} = await imports.axios.get("https://apidojo-yahoo-finance-v1.p.rapidapi.com/stock/v2/get-chart", {
|
||||||
params:{interval, symbol, range:duration, region:"US"},
|
params: {interval, symbol, range: duration, region: "US"},
|
||||||
headers:{"x-rapidapi-key":token},
|
headers: {"x-rapidapi-key": token},
|
||||||
})
|
})
|
||||||
const {currency, regularMarketPrice:price, previousClose:previous} = meta
|
const {currency, regularMarketPrice: price, previousClose: previous} = meta
|
||||||
|
|
||||||
//Generating chart
|
//Generating chart
|
||||||
console.debug(`metrics/compute/${login}/plugins > stock > generating chart`)
|
console.debug(`metrics/compute/${login}/plugins > stock > generating chart`)
|
||||||
const chart = await imports.chartist("line", {
|
const chart = await imports.chartist("line", {
|
||||||
width:480 * (1 + data.large),
|
width: 480 * (1 + data.large),
|
||||||
height:160,
|
height: 160,
|
||||||
showPoint:false,
|
showPoint: false,
|
||||||
axisX:{showGrid:false, labelInterpolationFnc:(value, index) => index % Math.floor(close.length / 4) === 0 ? value : null},
|
axisX: {showGrid: false, labelInterpolationFnc: (value, index) => index % Math.floor(close.length / 4) === 0 ? value : null},
|
||||||
axisY:{scaleMinSpace:20},
|
axisY: {scaleMinSpace: 20},
|
||||||
showArea:true,
|
showArea: true,
|
||||||
}, {
|
}, {
|
||||||
labels:timestamp.map(timestamp => new Intl.DateTimeFormat("en-GB", {month:"2-digit", day:"2-digit", hour:"2-digit", minute:"2-digit"}).format(new Date(timestamp * 1000))),
|
labels: timestamp.map(timestamp => new Intl.DateTimeFormat("en-GB", {month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"}).format(new Date(timestamp * 1000))),
|
||||||
series:[close],
|
series: [close],
|
||||||
})
|
})
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {chart, currency, price, previous, delta:price - previous, symbol, company, interval, duration}
|
return {chart, currency, price, previous, delta: price - previous, symbol, company, interval, duration}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -55,6 +55,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
}
|
}
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {head, base, ignored, contributions, sections, categories} = imports.metadata.plugins.contributors.inputs({data, account, q})
|
let {head, base, ignored, contributions, sections, categories} = imports.metadata.plugins.contributors.inputs({data, account, q})
|
||||||
const repo = {owner:data.repo.owner.login, repo:data.repo.name}
|
const repo = {owner: data.repo.owner.login, repo: data.repo.name}
|
||||||
ignored.push(...data.shared["users.ignored"])
|
ignored.push(...data.shared["users.ignored"])
|
||||||
|
|
||||||
//Retrieve head and base commits
|
//Retrieve head and base commits
|
||||||
console.debug(`metrics/compute/${login}/plugins > contributors > querying api head and base commits`)
|
console.debug(`metrics/compute/${login}/plugins > contributors > querying api head and base commits`)
|
||||||
const ref = {
|
const ref = {
|
||||||
head:(await graphql(queries.contributors.commit({...repo, expression:head}))).repository.object,
|
head: (await graphql(queries.contributors.commit({...repo, expression: head}))).repository.object,
|
||||||
base:(await graphql(queries.contributors.commit({...repo, expression:base}))).repository.object,
|
base: (await graphql(queries.contributors.commit({...repo, expression: base}))).repository.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
//Get commit activity
|
//Get commit activity
|
||||||
console.debug(`metrics/compute/${login}/plugins > contributors > querying api for commits between [${ref.base?.abbreviatedOid ?? null}] and [${ref.head?.abbreviatedOid ?? null}]`)
|
console.debug(`metrics/compute/${login}/plugins > contributors > querying api for commits between [${ref.base?.abbreviatedOid ?? null}] and [${ref.head?.abbreviatedOid ?? null}]`)
|
||||||
const commits = []
|
const commits = []
|
||||||
for (let page = 1; ; page++) {
|
for (let page = 1;; page++) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > contributors > loading page ${page}`)
|
console.debug(`metrics/compute/${login}/plugins > contributors > loading page ${page}`)
|
||||||
try {
|
try {
|
||||||
const {data:loaded} = await rest.repos.listCommits({...repo, per_page:100, page})
|
const {data: loaded} = await rest.repos.listCommits({...repo, per_page: 100, page})
|
||||||
if (loaded.map(({sha}) => sha).includes(ref.base?.oid)) {
|
if (loaded.map(({sha}) => sha).includes(ref.base?.oid)) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > contributors > reached ${ref.base?.oid}`)
|
console.debug(`metrics/compute/${login}/plugins > contributors > reached ${ref.base?.oid}`)
|
||||||
commits.push(...loaded.slice(0, loaded.map(({sha}) => sha).indexOf(ref.base.oid)))
|
commits.push(...loaded.slice(0, loaded.map(({sha}) => sha).indexOf(ref.base.oid)))
|
||||||
@@ -50,13 +50,13 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
|
|
||||||
//Compute contributors and contributions
|
//Compute contributors and contributions
|
||||||
let contributors = {}
|
let contributors = {}
|
||||||
for (const {author:{login, avatar_url:avatar}, commit:{message = "", author:{email = ""} = {}}} of commits) {
|
for (const {author: {login, avatar_url: avatar}, commit: {message = "", author: {email = ""} = {}}} of commits) {
|
||||||
if ((!login) || (ignored.includes(login)) || (ignored.includes(email))) {
|
if ((!login) || (ignored.includes(login)) || (ignored.includes(email))) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > contributors > ignored contributor "${login}"`)
|
console.debug(`metrics/compute/${login}/plugins > contributors > ignored contributor "${login}"`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (!(login in contributors))
|
if (!(login in contributors))
|
||||||
contributors[login] = {avatar:await imports.imgb64(avatar), contributions:1, pr:[]}
|
contributors[login] = {avatar: await imports.imgb64(avatar), contributions: 1, pr: []}
|
||||||
else {
|
else {
|
||||||
contributors[login].contributions++
|
contributors[login].contributions++
|
||||||
contributors[login].pr.push(...(message.match(/(?<=[(])#\d+(?=[)])/g) ?? []))
|
contributors[login].pr.push(...(message.match(/(?<=[(])#\d+(?=[)])/g) ?? []))
|
||||||
@@ -78,8 +78,8 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
//Git clone into temporary directory
|
//Git clone into temporary directory
|
||||||
await imports.fs.rm(path, {recursive:true, force:true})
|
await imports.fs.rm(path, {recursive: true, force: true})
|
||||||
await imports.fs.mkdir(path, {recursive:true})
|
await imports.fs.mkdir(path, {recursive: true})
|
||||||
const git = await imports.git(path)
|
const git = await imports.git(path)
|
||||||
await git.clone(`https://github.com/${repository}`, ".").status()
|
await git.clone(`https://github.com/${repository}`, ".").status()
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
for (const contributor in contributors) {
|
for (const contributor in contributors) {
|
||||||
//Load edited files by contributor
|
//Load edited files by contributor
|
||||||
const files = []
|
const files = []
|
||||||
await imports.spawn("git", ["--no-pager", "log", `--author="${contributor}"`, "--regexp-ignore-case", "--no-merges", "--name-only", '--pretty=format:""'], {cwd:path}, {
|
await imports.spawn("git", ["--no-pager", "log", `--author="${contributor}"`, "--regexp-ignore-case", "--no-merges", "--name-only", '--pretty=format:""'], {cwd: path}, {
|
||||||
stdout(line) {
|
stdout(line) {
|
||||||
if (line.trim().length)
|
if (line.trim().length)
|
||||||
files.push(line)
|
files.push(line)
|
||||||
@@ -98,7 +98,7 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
for (const [category, globs] of Object.entries(categories)) {
|
for (const [category, globs] of Object.entries(categories)) {
|
||||||
for (const glob of [globs].flat(Infinity)) {
|
for (const glob of [globs].flat(Infinity)) {
|
||||||
if (imports.minimatch(file, glob, {nocase:true})) {
|
if (imports.minimatch(file, glob, {nocase: true})) {
|
||||||
types[category].add(contributor)
|
types[category].add(contributor)
|
||||||
continue filesloop
|
continue filesloop
|
||||||
}
|
}
|
||||||
@@ -114,15 +114,15 @@ export default async function({login, q, imports, data, rest, graphql, queries,
|
|||||||
finally {
|
finally {
|
||||||
//Cleaning
|
//Cleaning
|
||||||
console.debug(`metrics/compute/${login}/plugins > contributors > cleaning temp dir ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > contributors > cleaning temp dir ${path}`)
|
||||||
await imports.fs.rm(path, {recursive:true, force:true})
|
await imports.fs.rm(path, {recursive: true, force: true})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {head, base, ref, list:contributors, categories:types, contributions, sections}
|
return {head, base, ref, list: contributors, categories: types, contributions, sections}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q}, {conf, data, rest, graphql, plugins, queries, account, convert, template}, {pending, imports}) {
|
export default async function({login, q}, {conf, data, rest, graphql, plugins, queries, account, convert, template}, {pending, imports}) {
|
||||||
//Load inputs
|
//Load inputs
|
||||||
const {"config.animations":animations, "config.display":display, "config.timezone":_timezone, "config.base64":_base64, "debug.flags":dflags} = imports.metadata.plugins.core.inputs({data, account, q})
|
const {"config.animations": animations, "config.display": display, "config.timezone": _timezone, "config.base64": _base64, "debug.flags": dflags} = imports.metadata.plugins.core.inputs({data, account, q})
|
||||||
imports.metadata.templates[template].check({q, account, format:convert})
|
imports.metadata.templates[template].check({q, account, format: convert})
|
||||||
|
|
||||||
//Base64 images
|
//Base64 images
|
||||||
if (!_base64) {
|
if (!_base64) {
|
||||||
@@ -17,23 +17,23 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
|
|
||||||
//Init
|
//Init
|
||||||
const computed = {
|
const computed = {
|
||||||
commits:0,
|
commits: 0,
|
||||||
sponsorships:0,
|
sponsorships: 0,
|
||||||
licenses:{favorite:"", used:{}, about:{}},
|
licenses: {favorite: "", used: {}, about: {}},
|
||||||
token:{},
|
token: {},
|
||||||
repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_closed:0, pr_merged:0, forks:0, forked:0, releases:0, deployments:0, environments:0},
|
repositories: {watchers: 0, stargazers: 0, issues_open: 0, issues_closed: 0, pr_open: 0, pr_closed: 0, pr_merged: 0, forks: 0, forked: 0, releases: 0, deployments: 0, environments: 0},
|
||||||
}
|
}
|
||||||
const avatar = imports.imgb64(data.user.avatarUrl)
|
const avatar = imports.imgb64(data.user.avatarUrl)
|
||||||
data.computed = computed
|
data.computed = computed
|
||||||
console.debug(`metrics/compute/${login} > formatting common metrics`)
|
console.debug(`metrics/compute/${login} > formatting common metrics`)
|
||||||
|
|
||||||
//Timezone config
|
//Timezone config
|
||||||
const offset = Number(new Date().toLocaleString("fr", {timeZoneName:"short"}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0
|
const offset = Number(new Date().toLocaleString("fr", {timeZoneName: "short"}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0
|
||||||
if (_timezone) {
|
if (_timezone) {
|
||||||
const timezone = {name:_timezone, offset:0}
|
const timezone = {name: _timezone, offset: 0}
|
||||||
data.config.timezone = timezone
|
data.config.timezone = timezone
|
||||||
try {
|
try {
|
||||||
timezone.offset = offset - (Number(new Date().toLocaleString("fr", {timeZoneName:"short", timeZone:timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0)
|
timezone.offset = offset - (Number(new Date().toLocaleString("fr", {timeZoneName: "short", timeZone: timezone.name}).match(/UTC[+](?<offset>\d+)/)?.groups?.offset * 60 * 60 * 1000) || 0)
|
||||||
console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset / (60 * 60 * 1000))} hours)`)
|
console.debug(`metrics/compute/${login} > timezone set to ${timezone.name} (${timezone.offset > 0 ? "+" : ""}${Math.round(timezone.offset / (60 * 60 * 1000))} hours)`)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -41,9 +41,9 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`)
|
console.debug(`metrics/compute/${login} > failed to use timezone "${timezone.name}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (process?.env?.TZ)
|
else if (process?.env?.TZ) {
|
||||||
data.config.timezone = {name:process.env.TZ, offset}
|
data.config.timezone = {name: process.env.TZ, offset}
|
||||||
|
}
|
||||||
|
|
||||||
//Display
|
//Display
|
||||||
data.large = display === "large"
|
data.large = display === "large"
|
||||||
@@ -60,7 +60,7 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
pending.push((async () => {
|
pending.push((async () => {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
|
console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
|
||||||
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, {extras:conf.settings?.extras?.features ?? conf.settings?.extras?.default ?? false, sandbox:conf.settings?.sandbox ?? false, ...plugins[name]})
|
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries, account}, {extras: conf.settings?.extras?.features ?? conf.settings?.extras?.default ?? false, sandbox: conf.settings?.sandbox ?? false, ...plugins[name]})
|
||||||
console.debug(`metrics/compute/${login}/plugins > ${name} > completed`)
|
console.debug(`metrics/compute/${login}/plugins > ${name} > completed`)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -68,8 +68,8 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
data.plugins[name] = error
|
data.plugins[name] = error
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
const result = {name, result:data.plugins[name]}
|
const result = {name, result: data.plugins[name]}
|
||||||
console.debug(imports.util.inspect(result, {depth:Infinity, maxStringLength:256, getters:true}))
|
console.debug(imports.util.inspect(result, {depth: Infinity, maxStringLength: 256, getters: true}))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
})())
|
})())
|
||||||
@@ -108,7 +108,7 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
const months = diff.getUTCMonth() - new Date(0).getUTCMonth()
|
const months = diff.getUTCMonth() - new Date(0).getUTCMonth()
|
||||||
const days = diff.getUTCDate() - new Date(0).getUTCDate()
|
const days = diff.getUTCDate() - new Date(0).getUTCDate()
|
||||||
|
|
||||||
computed.registered = {years:years + days / 365.25, months}
|
computed.registered = {years: years + days / 365.25, months}
|
||||||
computed.registration = years ? `${years} year${imports.s(years)} ago` : months ? `${months} month${imports.s(months)} ago` : `${days} day${imports.s(days)} ago`
|
computed.registration = years ? `${years} year${imports.s(years)} ago` : months ? `${months} month${imports.s(months)} ago` : `${days} day${imports.s(days)} ago`
|
||||||
computed.cakeday = (years >= 1 && months === 0 && days === 0) ? true : false
|
computed.cakeday = (years >= 1 && months === 0 && days === 0) ? true : false
|
||||||
|
|
||||||
@@ -129,9 +129,9 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
|
|
||||||
//Meta
|
//Meta
|
||||||
data.meta = {
|
data.meta = {
|
||||||
version:conf.package.version,
|
version: conf.package.version,
|
||||||
author:conf.package.author,
|
author: conf.package.author,
|
||||||
generated:imports.format.date(new Date(), {date:true, time:true}),
|
generated: imports.format.date(new Date(), {date: true, time: true}),
|
||||||
}
|
}
|
||||||
|
|
||||||
//Debug flags
|
//Debug flags
|
||||||
@@ -146,7 +146,8 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
if (dflags.includes("--halloween")) {
|
if (dflags.includes("--halloween")) {
|
||||||
console.debug(`metrics/compute/${login} > applying dflag --halloween`)
|
console.debug(`metrics/compute/${login} > applying dflag --halloween`)
|
||||||
//Haloween color replacer
|
//Haloween color replacer
|
||||||
const halloween = content => content
|
const halloween = content =>
|
||||||
|
content
|
||||||
.replace(/--color-calendar-graph/g, "--color-calendar-halloween-graph")
|
.replace(/--color-calendar-graph/g, "--color-calendar-halloween-graph")
|
||||||
.replace(/#9be9a8/gi, "var(--color-calendar-halloween-graph-day-L1-bg)")
|
.replace(/#9be9a8/gi, "var(--color-calendar-halloween-graph-day-L1-bg)")
|
||||||
.replace(/#40c463/gi, "var(--color-calendar-halloween-graph-day-L2-bg)")
|
.replace(/#40c463/gi, "var(--color-calendar-halloween-graph-day-L2-bg)")
|
||||||
@@ -160,7 +161,7 @@ export default async function({login, q}, {conf, data, rest, graphql, plugins, q
|
|||||||
await Promise.all(waiting)
|
await Promise.all(waiting)
|
||||||
if (data.plugins.isocalendar?.svg)
|
if (data.plugins.isocalendar?.svg)
|
||||||
data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg)
|
data.plugins.isocalendar.svg = halloween(data.plugins.isocalendar.svg)
|
||||||
return {name:"dflag.halloween", result:true}
|
return {name: "dflag.halloween", result: true}
|
||||||
})())
|
})())
|
||||||
}
|
}
|
||||||
if (dflags.includes("--error")) {
|
if (dflags.includes("--error")) {
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
const {categories:_categories, "categories.limit":_categories_limit} = imports.metadata.plugins.discussions.inputs({data, account, q})
|
const {categories: _categories, "categories.limit": _categories_limit} = imports.metadata.plugins.discussions.inputs({data, account, q})
|
||||||
const discussions = {categories:{}, upvotes:{discussions:0, comments:0}}
|
const discussions = {categories: {}, upvotes: {discussions: 0, comments: 0}}
|
||||||
discussions.display = {categories:_categories ? {limit:_categories_limit || Infinity} : null}
|
discussions.display = {categories: _categories ? {limit: _categories_limit || Infinity} : null}
|
||||||
|
|
||||||
//Fetch general statistics
|
//Fetch general statistics
|
||||||
const stats = Object.fromEntries(Object.entries((await graphql(queries.discussions.statistics({login}))).user).map(([key, value]) => [key, value.totalCount]))
|
const stats = Object.fromEntries(Object.entries((await graphql(queries.discussions.statistics({login}))).user).map(([key, value]) => [key, value.totalCount]))
|
||||||
@@ -23,7 +23,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/discussions > retrieving discussions after ${cursor}`)
|
console.debug(`metrics/compute/${login}/discussions > retrieving discussions after ${cursor}`)
|
||||||
const {user:{repositoryDiscussions:{edges = [], nodes = []} = {}}} = await graphql(queries.discussions.categories({login, after:cursor ? `after: "${cursor}"` : ""}))
|
const {user: {repositoryDiscussions: {edges = [], nodes = []} = {}}} = await graphql(queries.discussions.categories({login, after: cursor ? `after: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
fetched.push(...nodes)
|
fetched.push(...nodes)
|
||||||
pushed = nodes.length
|
pushed = nodes.length
|
||||||
@@ -34,7 +34,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
fetched.map(({upvoteCount}) => discussions.upvotes.discussions += upvoteCount)
|
fetched.map(({upvoteCount}) => discussions.upvotes.discussions += upvoteCount)
|
||||||
|
|
||||||
//Compute favorite category
|
//Compute favorite category
|
||||||
for (const category of [...fetched.map(({category:{emoji, name}}) => `${imports.emoji.get(emoji) ?? emoji} ${name}`)])
|
for (const category of [...fetched.map(({category: {emoji, name}}) => `${imports.emoji.get(emoji) ?? emoji} ${name}`)])
|
||||||
categories[category] = (categories[category] ?? 0) + 1
|
categories[category] = (categories[category] ?? 0) + 1
|
||||||
const categoryEntries = Object.entries(categories).sort((a, b) => b[1] - a[1])
|
const categoryEntries = Object.entries(categories).sort((a, b) => b[1] - a[1])
|
||||||
discussions.categories.stats = Object.fromEntries(categoryEntries)
|
discussions.categories.stats = Object.fromEntries(categoryEntries)
|
||||||
@@ -48,7 +48,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/discussions > retrieving comments after ${cursor}`)
|
console.debug(`metrics/compute/${login}/discussions > retrieving comments after ${cursor}`)
|
||||||
const {user:{repositoryDiscussionComments:{edges = [], nodes = []} = {}}} = await graphql(queries.discussions.comments({login, after:cursor ? `after: "${cursor}"` : ""}))
|
const {user: {repositoryDiscussionComments: {edges = [], nodes = []} = {}}} = await graphql(queries.discussions.comments({login, after: cursor ? `after: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
fetched.push(...nodes)
|
fetched.push(...nodes)
|
||||||
pushed = nodes.length
|
pushed = nodes.length
|
||||||
@@ -64,6 +64,6 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
//Define getters
|
//Define getters
|
||||||
const followup = {
|
const followup = {
|
||||||
sections,
|
sections,
|
||||||
issues:{
|
issues: {
|
||||||
get count() {
|
get count() {
|
||||||
return this.open + this.closed + this.drafts + this.skipped
|
return this.open + this.closed + this.drafts + this.skipped
|
||||||
},
|
},
|
||||||
@@ -22,16 +22,16 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
get closed() {
|
get closed() {
|
||||||
return computed.repositories.issues_closed
|
return computed.repositories.issues_closed
|
||||||
},
|
},
|
||||||
drafts:0,
|
drafts: 0,
|
||||||
skipped:0,
|
skipped: 0,
|
||||||
collaborators:{
|
collaborators: {
|
||||||
open:0,
|
open: 0,
|
||||||
closed:0,
|
closed: 0,
|
||||||
drafts:0,
|
drafts: 0,
|
||||||
skipped:0,
|
skipped: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pr:{
|
pr: {
|
||||||
get count() {
|
get count() {
|
||||||
return this.open + this.closed + this.merged + this.drafts
|
return this.open + this.closed + this.merged + this.drafts
|
||||||
},
|
},
|
||||||
@@ -44,12 +44,12 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
get merged() {
|
get merged() {
|
||||||
return computed.repositories.pr_merged
|
return computed.repositories.pr_merged
|
||||||
},
|
},
|
||||||
drafts:0,
|
drafts: 0,
|
||||||
collaborators:{
|
collaborators: {
|
||||||
open:0,
|
open: 0,
|
||||||
closed:0,
|
closed: 0,
|
||||||
merged:0,
|
merged: 0,
|
||||||
drafts:0,
|
drafts: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -59,15 +59,15 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
//Indepth mode
|
//Indepth mode
|
||||||
if (indepth) {
|
if (indepth) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > followup > indepth`)
|
console.debug(`metrics/compute/${login}/plugins > followup > indepth`)
|
||||||
followup.indepth = {repositories:{}}
|
followup.indepth = {repositories: {}}
|
||||||
|
|
||||||
//Process repositories
|
//Process repositories
|
||||||
for (const {name:repo, owner:{login:owner}} of data.user.repositories.nodes) {
|
for (const {name: repo, owner: {login: owner}} of data.user.repositories.nodes) {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > followup > processing ${owner}/${repo}`)
|
console.debug(`metrics/compute/${login}/plugins > followup > processing ${owner}/${repo}`)
|
||||||
followup.indepth.repositories[`${owner}/${repo}`] = {stats:{}}
|
followup.indepth.repositories[`${owner}/${repo}`] = {stats: {}}
|
||||||
//Fetch users with push access
|
//Fetch users with push access
|
||||||
let {repository:{collaborators:{nodes:collaborators}}} = await graphql(queries.followup["repository.collaborators"]({repo, owner})).catch(() => ({repository:{collaborators:{nodes:[{login:owner}]}}}))
|
let {repository: {collaborators: {nodes: collaborators}}} = await graphql(queries.followup["repository.collaborators"]({repo, owner})).catch(() => ({repository: {collaborators: {nodes: [{login: owner}]}}}))
|
||||||
console.debug(`metrics/compute/${login}/plugins > followup > found ${collaborators.length} collaborators`)
|
console.debug(`metrics/compute/${login}/plugins > followup > found ${collaborators.length} collaborators`)
|
||||||
followup.indepth.repositories[`${owner}/${repo}`].collaborators = collaborators.map(({login}) => login)
|
followup.indepth.repositories[`${owner}/${repo}`].collaborators = collaborators.map(({login}) => login)
|
||||||
//Fetch issues and pull requests created by collaborators
|
//Fetch issues and pull requests created by collaborators
|
||||||
@@ -75,7 +75,7 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
const stats = await graphql(queries.followup.repository({repo, owner, collaborators}))
|
const stats = await graphql(queries.followup.repository({repo, owner, collaborators}))
|
||||||
followup.indepth.repositories[`${owner}/${repo}`] = stats
|
followup.indepth.repositories[`${owner}/${repo}`] = stats
|
||||||
//Aggregate global stats
|
//Aggregate global stats
|
||||||
for (const [key, {issueCount:count}] of Object.entries(stats)) {
|
for (const [key, {issueCount: count}] of Object.entries(stats)) {
|
||||||
const [section, type] = key.split("_")
|
const [section, type] = key.split("_")
|
||||||
followup[section].collaborators[type] += count
|
followup[section].collaborators[type] += count
|
||||||
}
|
}
|
||||||
@@ -92,23 +92,23 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
if ((account === "user") && (sections.includes("user"))) {
|
if ((account === "user") && (sections.includes("user"))) {
|
||||||
const search = await graphql(queries.followup.user({login}))
|
const search = await graphql(queries.followup.user({login}))
|
||||||
followup.user = {
|
followup.user = {
|
||||||
issues:{
|
issues: {
|
||||||
get count() {
|
get count() {
|
||||||
return this.open + this.closed + this.drafts + this.skipped
|
return this.open + this.closed + this.drafts + this.skipped
|
||||||
},
|
},
|
||||||
open:search.issues_open.issueCount,
|
open: search.issues_open.issueCount,
|
||||||
closed:search.issues_closed.issueCount,
|
closed: search.issues_closed.issueCount,
|
||||||
drafts:search.issues_drafts.issueCount,
|
drafts: search.issues_drafts.issueCount,
|
||||||
skipped:search.issues_skipped.issueCount,
|
skipped: search.issues_skipped.issueCount,
|
||||||
},
|
},
|
||||||
pr:{
|
pr: {
|
||||||
get count() {
|
get count() {
|
||||||
return this.open + this.closed + this.merged + this.drafts
|
return this.open + this.closed + this.merged + this.drafts
|
||||||
},
|
},
|
||||||
open:search.pr_open.issueCount,
|
open: search.pr_open.issueCount,
|
||||||
closed:search.pr_closed.issueCount,
|
closed: search.pr_closed.issueCount,
|
||||||
merged:search.pr_merged.issueCount,
|
merged: search.pr_merged.issueCount,
|
||||||
drafts:search.pr_drafts.issueCount,
|
drafts: search.pr_drafts.issueCount,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,6 @@ export default async function({login, data, computed, imports, q, graphql, queri
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > gists > retrieving gists after ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > gists > retrieving gists after ${cursor}`)
|
||||||
const {user:{gists:{edges, nodes, totalCount}}} = await graphql(queries.gists({login, after:cursor ? `after: "${cursor}"` : ""}))
|
const {user: {gists: {edges, nodes, totalCount}}} = await graphql(queries.gists({login, after: cursor ? `after: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
gists.push(...nodes)
|
gists.push(...nodes)
|
||||||
gists.totalCount = totalCount
|
gists.totalCount = totalCount
|
||||||
@@ -41,12 +41,12 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {totalCount:gists.totalCount, stargazers, forks, files, comments}
|
return {totalCount: gists.totalCount, stargazers, forks, files, comments}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//Legacy import
|
//Legacy import
|
||||||
import {recent as recent_analyzer} from "./../languages/analyzers.mjs"
|
import { recent as recent_analyzer } from "./../languages/analyzers.mjs"
|
||||||
|
|
||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, rest, imports, q, account}, {enabled = false, extras = false, ...defaults} = {}) {
|
export default async function({login, data, rest, imports, q, account}, {enabled = false, extras = false, ...defaults} = {}) {
|
||||||
@@ -10,10 +10,10 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {from, days, facts, charts, "charts.type":_charts, trim} = imports.metadata.plugins.habits.inputs({data, account, q}, defaults)
|
let {from, days, facts, charts, "charts.type": _charts, trim} = imports.metadata.plugins.habits.inputs({data, account, q}, defaults)
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
const habits = {facts, charts, trim, lines:{average:{chars:0}}, commits:{fetched:0, hour:NaN, hours:{}, day:NaN, days:{}}, indents:{style:"", spaces:0, tabs:0}, linguist:{available:false, ordered:[], languages:{}}}
|
const habits = {facts, charts, trim, lines: {average: {chars: 0}}, commits: {fetched: 0, hour: NaN, hours: {}, day: NaN, days: {}}, indents: {style: "", spaces: 0, tabs: 0}, linguist: {available: false, ordered: [], languages: {}}}
|
||||||
const pages = Math.ceil(from / 100)
|
const pages = Math.ceil(from / 100)
|
||||||
const offset = data.config.timezone?.offset ?? 0
|
const offset = data.config.timezone?.offset ?? 0
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
try {
|
try {
|
||||||
for (let page = 1; page <= pages; page++) {
|
for (let page = 1; page <= pages; page++) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > habits > loading page ${page}`)
|
console.debug(`metrics/compute/${login}/plugins > habits > loading page ${page}`)
|
||||||
events.push(...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data)
|
events.push(...(await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -51,8 +51,8 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
]
|
]
|
||||||
.filter(({status}) => status === "fulfilled")
|
.filter(({status}) => status === "fulfilled")
|
||||||
.map(({value}) => value)
|
.map(({value}) => value)
|
||||||
.flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), patch:file.patch ?? ""})))
|
.flatMap(files => files.map(file => ({name: imports.paths.basename(file.filename), patch: file.patch ?? ""})))
|
||||||
.map(({name, patch}) => ({name, patch:patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")}))
|
.map(({name, patch}) => ({name, patch: patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")}))
|
||||||
|
|
||||||
//Commit day
|
//Commit day
|
||||||
{
|
{
|
||||||
@@ -103,41 +103,41 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
if (patches.length) {
|
if (patches.length) {
|
||||||
//Call language analyzer (note: using content from other plugin is usually disallowed, this is mostly for legacy purposes)
|
//Call language analyzer (note: using content from other plugin is usually disallowed, this is mostly for legacy purposes)
|
||||||
habits.linguist.available = true
|
habits.linguist.available = true
|
||||||
const {total, stats} = await recent_analyzer({login, data, imports, rest, account}, {days, load:from || 1000, tempdir:"habits"})
|
const {total, stats} = await recent_analyzer({login, data, imports, rest, account}, {days, load: from || 1000, tempdir: "habits"})
|
||||||
habits.linguist.languages = Object.fromEntries(Object.entries(stats).map(([language, value]) => [language, value / total]))
|
habits.linguist.languages = Object.fromEntries(Object.entries(stats).map(([language, value]) => [language, value / total]))
|
||||||
habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([_an, a], [_bn, b]) => b - a)
|
habits.linguist.ordered = Object.entries(habits.linguist.languages).sort(([_an, a], [_bn, b]) => b - a)
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
console.debug(`metrics/compute/${login}/plugins > habits > linguist not available`)
|
console.debug(`metrics/compute/${login}/plugins > habits > linguist not available`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generating charts with chartist
|
//Generating charts with chartist
|
||||||
if (_charts === "chartist") {
|
if (_charts === "chartist") {
|
||||||
console.debug(`metrics/compute/${login}/plugins > habits > generating charts`)
|
console.debug(`metrics/compute/${login}/plugins > habits > generating charts`)
|
||||||
habits.charts = await Promise.all([
|
habits.charts = await Promise.all([
|
||||||
{type:"line", data:{...empty(24), ...Object.fromEntries(Object.entries(habits.commits.hours).filter(([k]) => !Number.isNaN(+k)))}, low:0, high:habits.commits.hours.max},
|
{type: "line", data: {...empty(24), ...Object.fromEntries(Object.entries(habits.commits.hours).filter(([k]) => !Number.isNaN(+k)))}, low: 0, high: habits.commits.hours.max},
|
||||||
{type:"line", data:{...empty(7), ...Object.fromEntries(Object.entries(habits.commits.days).filter(([k]) => !Number.isNaN(+k)))}, low:0, high:habits.commits.days.max, labels:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], half:true},
|
{type: "line", data: {...empty(7), ...Object.fromEntries(Object.entries(habits.commits.days).filter(([k]) => !Number.isNaN(+k)))}, low: 0, high: habits.commits.days.max, labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], half: true},
|
||||||
{type:"pie", data:habits.linguist.languages, half:true},
|
{type: "pie", data: habits.linguist.languages, half: true},
|
||||||
].map(({type, data, high, low, ref, labels = {}, half = false}) => {
|
].map(({type, data, high, low, ref, labels = {}, half = false}) => {
|
||||||
const options = {
|
const options = {
|
||||||
width:480 * (half ? 0.45 : 1),
|
width: 480 * (half ? 0.45 : 1),
|
||||||
height:160,
|
height: 160,
|
||||||
fullWidth:true,
|
fullWidth: true,
|
||||||
}
|
}
|
||||||
const values = {
|
const values = {
|
||||||
labels:Object.keys(data).map(key => labels[key] ?? key),
|
labels: Object.keys(data).map(key => labels[key] ?? key),
|
||||||
series:Object.values(data),
|
series: Object.values(data),
|
||||||
}
|
}
|
||||||
if (type === "line") {
|
if (type === "line") {
|
||||||
Object.assign(options, {
|
Object.assign(options, {
|
||||||
showPoint:true,
|
showPoint: true,
|
||||||
axisX:{showGrid:false},
|
axisX: {showGrid: false},
|
||||||
axisY:{showLabel:false, offset:20, labelInterpolationFnc:value => imports.format(value), high, low, referenceValue:ref},
|
axisY: {showLabel: false, offset: 20, labelInterpolationFnc: value => imports.format(value), high, low, referenceValue: ref},
|
||||||
showArea:true,
|
showArea: true,
|
||||||
})
|
})
|
||||||
Object.assign(values, {
|
Object.assign(values, {
|
||||||
series:[Object.values(data)],
|
series: [Object.values(data)],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return imports.chartist(type, options, values)
|
return imports.chartist(type, options, values)
|
||||||
@@ -166,7 +166,7 @@ export default async function({login, data, rest, imports, q, account}, {enabled
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,22 +10,22 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
let {title} = imports.metadata.plugins.introduction.inputs({data, account, q})
|
let {title} = imports.metadata.plugins.introduction.inputs({data, account, q})
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
let context = {mode:account, login}
|
let context = {mode: account, login}
|
||||||
if (q.repo) {
|
if (q.repo) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
|
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
|
||||||
const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift()
|
const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift()
|
||||||
context = {...context, mode:"repository", owner, repo}
|
context = {...context, mode: "repository", owner, repo}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Querying API
|
//Querying API
|
||||||
console.debug(`metrics/compute/${login}/plugins > introduction > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > introduction > querying api`)
|
||||||
const text = (await graphql(queries.introduction[context.mode](context)))[context.mode][{user:"bio", organization:"description", repository:"description"}[context.mode]]
|
const text = (await graphql(queries.introduction[context.mode](context)))[context.mode][{user: "bio", organization: "description", repository: "description"}[context.mode]]
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {mode:context.mode, title, text}
|
return {mode: context.mode, title, text}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
|
|
||||||
//Compute contribution calendar, highest contributions in a day, streaks and average commits per day
|
//Compute contribution calendar, highest contributions in a day, streaks and average commits per day
|
||||||
console.debug(`metrics/compute/${login}/plugins > isocalendar > computing stats`)
|
console.debug(`metrics/compute/${login}/plugins > isocalendar > computing stats`)
|
||||||
const calendar = {weeks:[]}
|
const calendar = {weeks: []}
|
||||||
const {streak, max, average} = await statistics({login, graphql, queries, start, end:now, calendar})
|
const {streak, max, average} = await statistics({login, graphql, queries, start, end: now, calendar})
|
||||||
const reference = Math.max(...calendar.weeks.flatMap(({contributionDays}) => contributionDays.map(({contributionCount}) => contributionCount)))
|
const reference = Math.max(...calendar.weeks.flatMap(({contributionDays}) => contributionDays.map(({contributionCount}) => contributionCount)))
|
||||||
|
|
||||||
//Compute SVG
|
//Compute SVG
|
||||||
@@ -43,8 +43,7 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
<feComponentTransfer>
|
<feComponentTransfer>
|
||||||
${[..."RGB"].map(channel => `<feFunc${channel} type="linear" slope="${1 - k * 0.4}" />`).join("")}
|
${[..."RGB"].map(channel => `<feFunc${channel} type="linear" slope="${1 - k * 0.4}" />`).join("")}
|
||||||
</feComponentTransfer>
|
</feComponentTransfer>
|
||||||
</filter>`
|
</filter>`)
|
||||||
)
|
|
||||||
.join("")
|
.join("")
|
||||||
}
|
}
|
||||||
<g transform="scale(4) translate(12, 0)">`
|
<g transform="scale(4) translate(12, 0)">`
|
||||||
@@ -75,13 +74,13 @@ export default async function({login, data, graphql, q, imports, queries, accoun
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Compute max and current streaks */
|
/**Compute max and current streaks */
|
||||||
async function statistics({login, graphql, queries, start, end, calendar}) {
|
async function statistics({login, graphql, queries, start, end, calendar}) {
|
||||||
let average = 0, max = 0, streak = {max:0, current:0}, values = []
|
let average = 0, max = 0, streak = {max: 0, current: 0}, values = []
|
||||||
//Load contribution calendar
|
//Load contribution calendar
|
||||||
for (let from = new Date(start); from < end;) {
|
for (let from = new Date(start); from < end;) {
|
||||||
//Set date range
|
//Set date range
|
||||||
@@ -97,7 +96,7 @@ async function statistics({login, graphql, queries, start, end, calendar}) {
|
|||||||
dto.setUTCMilliseconds(999)
|
dto.setUTCMilliseconds(999)
|
||||||
//Fetch data from api
|
//Fetch data from api
|
||||||
console.debug(`metrics/compute/${login}/plugins > isocalendar > loading calendar from "${from.toISOString()}" to "${dto.toISOString()}"`)
|
console.debug(`metrics/compute/${login}/plugins > isocalendar > loading calendar from "${from.toISOString()}" to "${dto.toISOString()}"`)
|
||||||
const {user:{calendar:{contributionCalendar:{weeks}}}} = await graphql(queries.isocalendar.calendar({login, from:from.toISOString(), to:dto.toISOString()}))
|
const {user: {calendar: {contributionCalendar: {weeks}}}} = await graphql(queries.isocalendar.calendar({login, from: from.toISOString(), to: dto.toISOString()}))
|
||||||
calendar.weeks.push(...weeks)
|
calendar.weeks.push(...weeks)
|
||||||
//Set next date range start
|
//Set next date range start
|
||||||
from = new Date(to)
|
from = new Date(to)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import linguist from "linguist-js"
|
|||||||
export async function indepth({login, data, imports, repositories, gpg}, {skipped, categories, timeout}) {
|
export async function indepth({login, data, imports, repositories, gpg}, {skipped, categories, timeout}) {
|
||||||
return new Promise(async solve => {
|
return new Promise(async solve => {
|
||||||
//Results
|
//Results
|
||||||
const results = {partial:false, total:0, lines:{}, stats:{}, colors:{}, commits:0, files:0, missed:{lines:0, bytes:0, commits:0}, verified:{signature:0}}
|
const results = {partial: false, total: 0, lines: {}, stats: {}, colors: {}, commits: 0, files: 0, missed: {lines: 0, bytes: 0, commits: 0}, verified: {signature: 0}}
|
||||||
|
|
||||||
//Timeout
|
//Timeout
|
||||||
if (Number.isFinite(timeout)) {
|
if (Number.isFinite(timeout)) {
|
||||||
@@ -27,9 +27,9 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe
|
|||||||
console.debug(`metrics/compute/${login}/plugins > languages > importing gpg ${id}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > importing gpg ${id}`)
|
||||||
await imports.run(`gpg --import ${path}`)
|
await imports.run(`gpg --import ${path}`)
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > skipping import of gpg ${id}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > skipping import of gpg ${id}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > an error occured while importing gpg ${id}, skipping...`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > an error occured while importing gpg ${id}, skipping...`)
|
||||||
@@ -37,7 +37,7 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe
|
|||||||
finally {
|
finally {
|
||||||
//Cleaning
|
//Cleaning
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning ${path}`)
|
||||||
await imports.fs.rm(path, {recursive:true, force:true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`))
|
await imports.fs.rm(path, {recursive: true, force: true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe
|
|||||||
//Process
|
//Process
|
||||||
try {
|
try {
|
||||||
//Git clone into temporary directory
|
//Git clone into temporary directory
|
||||||
await imports.fs.rm(path, {recursive:true, force:true})
|
await imports.fs.rm(path, {recursive: true, force: true})
|
||||||
await imports.fs.mkdir(path, {recursive:true})
|
await imports.fs.mkdir(path, {recursive: true})
|
||||||
const git = await imports.git(path)
|
const git = await imports.git(path)
|
||||||
await git.clone(`https://github.com/${repo}`, ".").status()
|
await git.clone(`https://github.com/${repo}`, ".").status()
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe
|
|||||||
finally {
|
finally {
|
||||||
//Cleaning
|
//Cleaning
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning temp dir ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > cleaning temp dir ${path}`)
|
||||||
await imports.fs.rm(path, {recursive:true, force:true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`))
|
await imports.fs.rm(path, {recursive: true, force: true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
solve(results)
|
solve(results)
|
||||||
@@ -89,7 +89,7 @@ export async function indepth({login, data, imports, repositories, gpg}, {skippe
|
|||||||
export async function recent({login, data, imports, rest, account}, {skipped = [], categories, days = 0, load = 0, tempdir = "recent", timeout}) {
|
export async function recent({login, data, imports, rest, account}, {skipped = [], categories, days = 0, load = 0, tempdir = "recent", timeout}) {
|
||||||
return new Promise(async solve => {
|
return new Promise(async solve => {
|
||||||
//Results
|
//Results
|
||||||
const results = {partial:false, total:0, lines:{}, stats:{}, colors:{}, commits:0, files:0, missed:{lines:0, bytes:0, commits:0}, days}
|
const results = {partial: false, total: 0, lines: {}, stats: {}, colors: {}, commits: 0, files: 0, missed: {lines: 0, bytes: 0, commits: 0}, days}
|
||||||
|
|
||||||
//Timeout
|
//Timeout
|
||||||
if (Number.isFinite(timeout)) {
|
if (Number.isFinite(timeout)) {
|
||||||
@@ -109,10 +109,10 @@ export async function recent({login, data, imports, rest, account}, {skipped = [
|
|||||||
for (let page = 1; page <= pages; page++) {
|
for (let page = 1; page <= pages; page++) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > loading page ${page}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > loading page ${page}`)
|
||||||
commits.push(
|
commits.push(
|
||||||
...(await rest.activity.listEventsForAuthenticatedUser({username:login, per_page:100, page})).data
|
...(await rest.activity.listEventsForAuthenticatedUser({username: login, per_page: 100, page})).data
|
||||||
.filter(({type}) => type === "PushEvent")
|
.filter(({type}) => type === "PushEvent")
|
||||||
.filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase())
|
.filter(({actor}) => account === "organization" ? true : actor.login?.toLocaleLowerCase() === login.toLocaleLowerCase())
|
||||||
.filter(({repo:{name:repo}}) => (!skipped.includes(repo.toLocaleLowerCase())) && (!skipped.includes(repo.toLocaleLowerCase().split("/").pop())))
|
.filter(({repo: {name: repo}}) => (!skipped.includes(repo.toLocaleLowerCase())) && (!skipped.includes(repo.toLocaleLowerCase().split("/").pop())))
|
||||||
.filter(({created_at}) => new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000)),
|
.filter(({created_at}) => new Date(created_at) > new Date(Date.now() - days * 24 * 60 * 60 * 1000)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -139,8 +139,8 @@ export async function recent({login, data, imports, rest, account}, {skipped = [
|
|||||||
.map(({value}) => value)
|
.map(({value}) => value)
|
||||||
.filter(({parents}) => parents.length <= 1)
|
.filter(({parents}) => parents.length <= 1)
|
||||||
.map(({files}) => files)
|
.map(({files}) => files)
|
||||||
.flatMap(files => files.map(file => ({name:imports.paths.basename(file.filename), directory:imports.paths.dirname(file.filename), patch:file.patch ?? "", repo:file.raw_url?.match(/(?<=^https:..github.com\/)(?<repo>.*)(?=\/raw)/)?.groups.repo ?? "_"})))
|
.flatMap(files => files.map(file => ({name: imports.paths.basename(file.filename), directory: imports.paths.dirname(file.filename), patch: file.patch ?? "", repo: file.raw_url?.match(/(?<=^https:..github.com\/)(?<repo>.*)(?=\/raw)/)?.groups.repo ?? "_"})))
|
||||||
.map(({name, directory, patch, repo}) => ({name, directory:`${repo.replace(/[/]/g, "@")}/${directory}`, patch:patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")}))
|
.map(({name, directory, patch, repo}) => ({name, directory: `${repo.replace(/[/]/g, "@")}/${directory}`, patch: patch.split("\n").filter(line => /^[+]/.test(line)).map(line => line.substring(1)).join("\n")}))
|
||||||
|
|
||||||
//Temporary directory
|
//Temporary directory
|
||||||
const path = imports.paths.join(imports.os.tmpdir(), `${data.user.databaseId}-${tempdir}`)
|
const path = imports.paths.join(imports.os.tmpdir(), `${data.user.databaseId}-${tempdir}`)
|
||||||
@@ -149,10 +149,10 @@ export async function recent({login, data, imports, rest, account}, {skipped = [
|
|||||||
//Process
|
//Process
|
||||||
try {
|
try {
|
||||||
//Save patches in temporary directory matching respective repository and filename
|
//Save patches in temporary directory matching respective repository and filename
|
||||||
await imports.fs.rm(path, {recursive:true, force:true})
|
await imports.fs.rm(path, {recursive: true, force: true})
|
||||||
await imports.fs.mkdir(path, {recursive:true})
|
await imports.fs.mkdir(path, {recursive: true})
|
||||||
await Promise.all(patches.map(async ({name, directory, patch}) => {
|
await Promise.all(patches.map(async ({name, directory, patch}) => {
|
||||||
await imports.fs.mkdir(imports.paths.join(path, directory), {recursive:true})
|
await imports.fs.mkdir(imports.paths.join(path, directory), {recursive: true})
|
||||||
await imports.fs.writeFile(imports.paths.join(path, directory, name), patch)
|
await imports.fs.writeFile(imports.paths.join(path, directory, name), patch)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ export async function recent({login, data, imports, rest, account}, {skipped = [
|
|||||||
await git.init().add(".").addConfig("user.name", data.shared["commits.authoring"]?.[0] ?? login).addConfig("user.email", "<>").commit("linguist").status()
|
await git.init().add(".").addConfig("user.name", data.shared["commits.authoring"]?.[0] ?? login).addConfig("user.email", "<>").commit("linguist").status()
|
||||||
|
|
||||||
//Analyze repository
|
//Analyze repository
|
||||||
await analyze(arguments[0], {results, path:imports.paths.join(path, directory), categories})
|
await analyze(arguments[0], {results, path: imports.paths.join(path, directory), categories})
|
||||||
|
|
||||||
//Since we reproduce a "partial repository" with a single commit, use number of commits retrieved instead
|
//Since we reproduce a "partial repository" with a single commit, use number of commits retrieved instead
|
||||||
results.commits = commits.length
|
results.commits = commits.length
|
||||||
@@ -189,7 +189,7 @@ export async function recent({login, data, imports, rest, account}, {skipped = [
|
|||||||
finally {
|
finally {
|
||||||
//Cleaning
|
//Cleaning
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > cleaning temp dir ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > cleaning temp dir ${path}`)
|
||||||
await imports.fs.rm(path, {recursive:true, force:true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`))
|
await imports.fs.rm(path, {recursive: true, force: true}).catch(error => console.debug(`metrics/compute/${login}/plugins > languages > indepth > failed to clean ${path} (${error})`))
|
||||||
}
|
}
|
||||||
solve(results)
|
solve(results)
|
||||||
})
|
})
|
||||||
@@ -199,7 +199,7 @@ export async function recent({login, data, imports, rest, account}, {skipped = [
|
|||||||
async function analyze({login, imports, data}, {results, path, categories = ["programming", "markup"]}) {
|
async function analyze({login, imports, data}, {results, path, categories = ["programming", "markup"]}) {
|
||||||
//Gather language data
|
//Gather language data
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > running linguist`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > running linguist`)
|
||||||
const {files:{results:files}, languages:{results:languageResults}} = await linguist(path)
|
const {files: {results: files}, languages: {results: languageResults}} = await linguist(path)
|
||||||
Object.assign(results.colors, Object.fromEntries(Object.entries(languageResults).map(([lang, {color}]) => [lang, color])))
|
Object.assign(results.colors, Object.fromEntries(Object.entries(languageResults).map(([lang, {color}]) => [lang, color])))
|
||||||
|
|
||||||
//Processing diff
|
//Processing diff
|
||||||
@@ -207,18 +207,18 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr
|
|||||||
const edited = new Set()
|
const edited = new Set()
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > checking git log`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > checking git log`)
|
||||||
try {
|
try {
|
||||||
await imports.run("git log --max-count=1", {cwd:path})
|
await imports.run("git log --max-count=1", {cwd: path})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > repo seems empty or impossible to git log, skipping`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > repo seems empty or impossible to git log, skipping`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pending = []
|
const pending = []
|
||||||
for (let page = 0; ; page++) {
|
for (let page = 0;; page++) {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > indepth > processing commits ${page * per_page} from ${(page + 1) * per_page}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > indepth > processing commits ${page * per_page} from ${(page + 1) * per_page}`)
|
||||||
let empty = true, file = null, lang = null
|
let empty = true, file = null, lang = null
|
||||||
await imports.spawn("git", ["log", ...data.shared["commits.authoring"].map(authoring => `--author="${authoring}"`), "--regexp-ignore-case", "--format=short", "--no-merges", "--patch", `--max-count=${per_page}`, `--skip=${page * per_page}`], {cwd:path}, {
|
await imports.spawn("git", ["log", ...data.shared["commits.authoring"].map(authoring => `--author="${authoring}"`), "--regexp-ignore-case", "--format=short", "--no-merges", "--patch", `--max-count=${per_page}`, `--skip=${page * per_page}`], {cwd: path}, {
|
||||||
stdout(line) {
|
stdout(line) {
|
||||||
try {
|
try {
|
||||||
//Unflag empty output
|
//Unflag empty output
|
||||||
@@ -230,7 +230,7 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr
|
|||||||
const sha = line.match(/[0-9a-f]{40}/)?.[0]
|
const sha = line.match(/[0-9a-f]{40}/)?.[0]
|
||||||
if (sha) {
|
if (sha) {
|
||||||
pending.push(
|
pending.push(
|
||||||
imports.run(`git verify-commit ${sha}`, {cwd:path, env:{LANG:"en_GB"}}, {log:false, prefixed:false})
|
imports.run(`git verify-commit ${sha}`, {cwd: path, env: {LANG: "en_GB"}}, {log: false, prefixed: false})
|
||||||
.then(() => results.verified.signature++)
|
.then(() => results.verified.signature++)
|
||||||
.catch(() => null),
|
.catch(() => null),
|
||||||
)
|
)
|
||||||
@@ -289,26 +289,26 @@ async function analyze({login, imports, data}, {results, path, categories = ["pr
|
|||||||
|
|
||||||
//import.meta.main
|
//import.meta.main
|
||||||
if (/languages.analyzers.mjs$/.test(process.argv[1])) {
|
if (/languages.analyzers.mjs$/.test(process.argv[1])) {
|
||||||
(async function() {
|
;(async function() {
|
||||||
//Parse inputs
|
//Parse inputs
|
||||||
const [_authoring, path] = process.argv.slice(2)
|
const [_authoring, path] = process.argv.slice(2)
|
||||||
if ((!_authoring) || (!path)) {
|
if ((!_authoring) || (!path)) {
|
||||||
console.log("Usage is:\n npm run indepth -- <commits authoring> <repository local path>\n\n")
|
console.log("Usage is:\n npm run indepth -- <commits authoring> <repository local path>\n\n")
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
const {default:setup} = await import("../../app/metrics/setup.mjs")
|
const {default: setup} = await import("../../app/metrics/setup.mjs")
|
||||||
const {conf:{metadata}} = await setup({log:false})
|
const {conf: {metadata}} = await setup({log: false})
|
||||||
const {"commits.authoring":authoring} = await metadata.plugins.base.inputs({q:{"commits.authoring":_authoring}, account:"bypass"})
|
const {"commits.authoring": authoring} = await metadata.plugins.base.inputs({q: {"commits.authoring": _authoring}, account: "bypass"})
|
||||||
const data = {shared:{"commits.authoring":authoring}}
|
const data = {shared: {"commits.authoring": authoring}}
|
||||||
|
|
||||||
//Prepare call
|
//Prepare call
|
||||||
const imports = await import("../../app/metrics/utils.mjs")
|
const imports = await import("../../app/metrics/utils.mjs")
|
||||||
const results = {total:0, lines:{}, colors:{}, stats:{}, missed:{lines:0, bytes:0, commits:0}}
|
const results = {total: 0, lines: {}, colors: {}, stats: {}, missed: {lines: 0, bytes: 0, commits: 0}}
|
||||||
console.debug = log => /exited with code null/.test(log) ? console.error(log.replace(/^.*--max-count=(?<step>\d+) --skip=(?<start>\d+).*$/, (_, step, start) => `error: skipped commits ${start} from ${Number(start) + Number(step)}`)) : null
|
console.debug = log => /exited with code null/.test(log) ? console.error(log.replace(/^.*--max-count=(?<step>\d+) --skip=(?<start>\d+).*$/, (_, step, start) => `error: skipped commits ${start} from ${Number(start) + Number(step)}`)) : null
|
||||||
|
|
||||||
//Analyze repository
|
//Analyze repository
|
||||||
console.log(`commits authoring | ${authoring}\nrepository path | ${path}\n`)
|
console.log(`commits authoring | ${authoring}\nrepository path | ${path}\n`)
|
||||||
await analyze({login:"cli", data, imports}, {results, path})
|
await analyze({login: "cli", data, imports}, {results, path})
|
||||||
console.log(results)
|
console.log(results)
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//Imports
|
//Imports
|
||||||
import {indepth as indepth_analyzer, recent as recent_analyzer} from "./analyzers.mjs"
|
import { indepth as indepth_analyzer, recent as recent_analyzer } from "./analyzers.mjs"
|
||||||
|
|
||||||
//Setup
|
//Setup
|
||||||
export default async function({login, data, imports, q, rest, account}, {enabled = false, extras = false} = {}) {
|
export default async function({login, data, imports, q, rest, account}, {enabled = false, extras = false} = {}) {
|
||||||
@@ -10,14 +10,14 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
let context = {mode:"user"}
|
let context = {mode: "user"}
|
||||||
if (q.repo) {
|
if (q.repo) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > switched to repository mode`)
|
console.debug(`metrics/compute/${login}/plugins > languages > switched to repository mode`)
|
||||||
context = {...context, mode:"repository"}
|
context = {...context, mode: "repository"}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {ignored, skipped, other, colors, aliases, details, threshold, limit, indepth, "analysis.timeout":timeout, sections, categories, "recent.categories":_recent_categories, "recent.load":_recent_load, "recent.days":_recent_days} = imports.metadata.plugins.languages
|
let {ignored, skipped, other, colors, aliases, details, threshold, limit, indepth, "analysis.timeout": timeout, sections, categories, "recent.categories": _recent_categories, "recent.load": _recent_load, "recent.days": _recent_days} = imports.metadata.plugins.languages
|
||||||
.inputs({
|
.inputs({
|
||||||
data,
|
data,
|
||||||
account,
|
account,
|
||||||
@@ -40,11 +40,11 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
|
|
||||||
//Unique languages
|
//Unique languages
|
||||||
const repositories = [...data.user.repositories.nodes, ...data.user.repositoriesContributedTo.nodes]
|
const repositories = [...data.user.repositories.nodes, ...data.user.repositoriesContributedTo.nodes]
|
||||||
const unique = new Set(repositories.flatMap(repository => repository.languages.edges.map(({node:{name}}) => name))).size
|
const unique = new Set(repositories.flatMap(repository => repository.languages.edges.map(({node: {name}}) => name))).size
|
||||||
|
|
||||||
//Iterate through user's repositories and retrieve languages data
|
//Iterate through user's repositories and retrieve languages data
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > processing ${data.user.repositories.nodes.length} repositories`)
|
console.debug(`metrics/compute/${login}/plugins > languages > processing ${data.user.repositories.nodes.length} repositories`)
|
||||||
const languages = {unique, sections, details, indepth, colors:{}, total:0, stats:{}, "stats.recent":{}}
|
const languages = {unique, sections, details, indepth, colors: {}, total: 0, stats: {}, "stats.recent": {}}
|
||||||
const customColors = {}
|
const customColors = {}
|
||||||
for (const repository of data.user.repositories.nodes) {
|
for (const repository of data.user.repositories.nodes) {
|
||||||
//Skip repository if asked
|
//Skip repository if asked
|
||||||
@@ -53,7 +53,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//Process repository languages
|
//Process repository languages
|
||||||
for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) {
|
for (const {size, node: {color, name}} of Object.values(repository.languages.edges)) {
|
||||||
languages.stats[name] = (languages.stats[name] ?? 0) + size
|
languages.stats[name] = (languages.stats[name] ?? 0) + size
|
||||||
if (colors[name.toLocaleLowerCase()])
|
if (colors[name.toLocaleLowerCase()])
|
||||||
customColors[name] = colors[name.toLocaleLowerCase()]
|
customColors[name] = colors[name.toLocaleLowerCase()]
|
||||||
@@ -69,7 +69,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
if ((sections.includes("recently-used")) && (context.mode === "user")) {
|
if ((sections.includes("recently-used")) && (context.mode === "user")) {
|
||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > using recent analyzer`)
|
console.debug(`metrics/compute/${login}/plugins > languages > using recent analyzer`)
|
||||||
languages["stats.recent"] = await recent_analyzer({login, data, imports, rest, account}, {skipped, categories:_recent_categories ?? categories, days:_recent_days, load:_recent_load, timeout})
|
languages["stats.recent"] = await recent_analyzer({login, data, imports, rest, account}, {skipped, categories: _recent_categories ?? categories, days: _recent_days, load: _recent_load, timeout})
|
||||||
Object.assign(languages.colors, languages["stats.recent"].colors)
|
Object.assign(languages.colors, languages["stats.recent"].colors)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -83,8 +83,8 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
const gpg = []
|
const gpg = []
|
||||||
try {
|
try {
|
||||||
for (const username of [login, "web-flow"]) {
|
for (const username of [login, "web-flow"]) {
|
||||||
const {data:keys} = await rest.users.listGpgKeysForUser({username})
|
const {data: keys} = await rest.users.listGpgKeysForUser({username})
|
||||||
gpg.push(...keys.map(({key_id:id, raw_key:pub, emails}) => ({id, pub, emails})))
|
gpg.push(...keys.map(({key_id: id, raw_key: pub, emails}) => ({id, pub, emails})))
|
||||||
if (username === login) {
|
if (username === login) {
|
||||||
for (const {email} of gpg.flatMap(({emails}) => emails)) {
|
for (const {email} of gpg.flatMap(({emails}) => emails)) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > auto-adding ${email} to commits_authoring (fetched from gpg)`)
|
console.debug(`metrics/compute/${login}/plugins > languages > auto-adding ${email} to commits_authoring (fetched from gpg)`)
|
||||||
@@ -126,9 +126,10 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Compute languages stats
|
//Compute languages stats
|
||||||
for (const {section, stats = {}, lines = {}, missed = {bytes:0}, total = 0} of [{section:"favorites", stats:languages.stats, lines:languages.lines, total:languages.total, missed:languages.missed}, {section:"recent", ...languages["stats.recent"]}]) {
|
for (const {section, stats = {}, lines = {}, missed = {bytes: 0}, total = 0} of [{section: "favorites", stats: languages.stats, lines: languages.lines, total: languages.total, missed: languages.missed}, {section: "recent", ...languages["stats.recent"]}]) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > languages > computing stats ${section}`)
|
console.debug(`metrics/compute/${login}/plugins > languages > computing stats ${section}`)
|
||||||
languages[section] = Object.entries(stats).filter(([name]) => !ignored.includes(name.toLocaleLowerCase())).sort(([_an, a], [_bn, b]) => b - a).slice(0, limit).map(([name, value]) => ({name, value, size:value, color:languages.colors[name], x:0})).filter(({value}) => value / total > threshold
|
languages[section] = Object.entries(stats).filter(([name]) => !ignored.includes(name.toLocaleLowerCase())).sort(([_an, a], [_bn, b]) => b - a).slice(0, limit).map(([name, value]) => ({name, value, size: value, color: languages.colors[name], x: 0})).filter(({value}) =>
|
||||||
|
value / total > threshold
|
||||||
)
|
)
|
||||||
if (other) {
|
if (other) {
|
||||||
let value = indepth ? missed.bytes : Object.entries(stats).filter(([name]) => !Object.values(languages[section]).map(({name}) => name).includes(name)).reduce((a, [_, b]) => a + b, 0)
|
let value = indepth ? missed.bytes : Object.entries(stats).filter(([name]) => !Object.values(languages[section]).map(({name}) => name).includes(name)).reduce((a, [_, b]) => a + b, 0)
|
||||||
@@ -141,7 +142,7 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
languages[section].push({name:"Other", value, size:value, get lines() { return missed.lines }, set lines(_) { }, x:0}) //eslint-disable-line brace-style, no-empty-function, max-statements-per-line
|
languages[section].push({name:"Other", value, size:value, get lines() { return missed.lines }, set lines(_) { }, x:0}) //eslint-disable-line brace-style, no-empty-function, max-statements-per-line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const visible = {total:Object.values(languages[section]).map(({size}) => size).reduce((a, b) => a + b, 0)}
|
const visible = {total: Object.values(languages[section]).map(({size}) => size).reduce((a, b) => a + b, 0)}
|
||||||
for (let i = 0; i < languages[section].length; i++) {
|
for (let i = 0; i < languages[section].length; i++) {
|
||||||
const {name} = languages[section][i]
|
const {name} = languages[section][i]
|
||||||
languages[section][i].value /= visible.total
|
languages[section][i].value /= visible.total
|
||||||
@@ -159,6 +160,6 @@ export default async function({login, data, imports, q, rest, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
let {setup, ratio, legal} = imports.metadata.plugins.licenses.inputs({data, account, q})
|
let {setup, ratio, legal} = imports.metadata.plugins.licenses.inputs({data, account, q})
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
const {user:{repository}} = await graphql(queries.licenses.repository({owner:data.repo.owner.login, name:data.repo.name, account}))
|
const {user: {repository}} = await graphql(queries.licenses.repository({owner: data.repo.owner.login, name: data.repo.name, account}))
|
||||||
const result = {ratio, legal, default:repository.licenseInfo, licensed:{available:false}, text:{}, list:[], used:{}, dependencies:[], known:0, unknown:0}
|
const result = {ratio, legal, default: repository.licenseInfo, licensed: {available: false}, text: {}, list: [], used: {}, dependencies: [], known: 0, unknown: 0}
|
||||||
const {used, text} = result
|
const {used, text} = result
|
||||||
|
|
||||||
//Register existing licenses properties
|
//Register existing licenses properties
|
||||||
@@ -29,8 +29,8 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
const path = imports.paths.join(imports.os.tmpdir(), `${repository.databaseId}`)
|
const path = imports.paths.join(imports.os.tmpdir(), `${repository.databaseId}`)
|
||||||
//Create temporary directory
|
//Create temporary directory
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > creating temp dir ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > creating temp dir ${path}`)
|
||||||
await imports.fs.rm(path, {recursive:true, force:true})
|
await imports.fs.rm(path, {recursive: true, force: true})
|
||||||
await imports.fs.mkdir(path, {recursive:true})
|
await imports.fs.mkdir(path, {recursive: true})
|
||||||
//Clone repository
|
//Clone repository
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > cloning temp git repository ${repository.url} to ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > cloning temp git repository ${repository.url} to ${path}`)
|
||||||
const git = imports.git(path)
|
const git = imports.git(path)
|
||||||
@@ -38,7 +38,7 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
//Run setup
|
//Run setup
|
||||||
if (setup) {
|
if (setup) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > running setup [${setup}]`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > running setup [${setup}]`)
|
||||||
await imports.run(setup, {cwd:path}, {prefixed:false})
|
await imports.run(setup, {cwd: path}, {prefixed: false})
|
||||||
}
|
}
|
||||||
//Create configuration file if needed
|
//Create configuration file if needed
|
||||||
if (!(await imports.fs.stat(imports.paths.join(path, ".licensed.yml")).then(() => 1).catch(() => 0))) {
|
if (!(await imports.fs.stat(imports.paths.join(path, ".licensed.yml")).then(() => 1).catch(() => 0))) {
|
||||||
@@ -50,33 +50,35 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > a .licensed.yml configuration file already exists`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > a .licensed.yml configuration file already exists`)
|
||||||
|
}
|
||||||
|
|
||||||
//Spawn licensed process
|
//Spawn licensed process
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > running licensed`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > running licensed`)
|
||||||
JSON.parse(await imports.run("licensed list --format=json --licenses", {cwd:path})).apps
|
JSON.parse(await imports.run("licensed list --format=json --licenses", {cwd: path})).apps
|
||||||
.map(({sources}) => sources?.flatMap(source => source.dependencies?.map(({dependency, license}) => {
|
.map(({sources}) =>
|
||||||
|
sources?.flatMap(source =>
|
||||||
|
source.dependencies?.map(({dependency, license}) => {
|
||||||
used[license] = (used[license] ?? 0) + 1
|
used[license] = (used[license] ?? 0) + 1
|
||||||
result.dependencies.push(dependency)
|
result.dependencies.push(dependency)
|
||||||
result.known += (license in licenses)
|
result.known += license in licenses
|
||||||
result.unknown += !(license in licenses)
|
result.unknown += !(license in licenses)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
//Cleaning
|
//Cleaning
|
||||||
console.debug(`metrics/compute/${login}/plugins > licensed > cleaning temp dir ${path}`)
|
console.debug(`metrics/compute/${login}/plugins > licensed > cleaning temp dir ${path}`)
|
||||||
await imports.fs.rm(path, {recursive:true, force:true})
|
await imports.fs.rm(path, {recursive: true, force: true})
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > licensed not available`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > licensed not available`)
|
||||||
|
}
|
||||||
|
|
||||||
//List licenses properties
|
//List licenses properties
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > compute licenses properties`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > compute licenses properties`)
|
||||||
const base = {permissions:new Set(), limitations:new Set(), conditions:new Set()}
|
const base = {permissions: new Set(), limitations: new Set(), conditions: new Set()}
|
||||||
const combined = {permissions:new Set(), limitations:new Set(), conditions:new Set()}
|
const combined = {permissions: new Set(), limitations: new Set(), conditions: new Set()}
|
||||||
const detected = Object.entries(used).map(([key, _value]) => ({key}))
|
const detected = Object.entries(used).map(([key, _value]) => ({key}))
|
||||||
for (const properties of Object.keys(base)) {
|
for (const properties of Object.keys(base)) {
|
||||||
//Base license
|
//Base license
|
||||||
@@ -89,15 +91,15 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
|
|
||||||
//Merge limitations and conditions
|
//Merge limitations and conditions
|
||||||
for (const properties of ["limitations", "conditions"])
|
for (const properties of ["limitations", "conditions"])
|
||||||
result[properties] = [[...base[properties]].map(key => ({key, text:text[key], inherited:false})), [...combined[properties]].filter(key => !base[properties].has(key)).map(key => ({key, text:text[key], inherited:true}))].flat()
|
result[properties] = [[...base[properties]].map(key => ({key, text: text[key], inherited: false})), [...combined[properties]].filter(key => !base[properties].has(key)).map(key => ({key, text: text[key], inherited: true}))].flat()
|
||||||
//Remove base permissions conflicting with inherited limitations
|
//Remove base permissions conflicting with inherited limitations
|
||||||
result.permissions = [...base.permissions].filter(key => !combined.limitations.has(key)).map(key => ({key, text:text[key]}))
|
result.permissions = [...base.permissions].filter(key => !combined.limitations.has(key)).map(key => ({key, text: text[key]}))
|
||||||
|
|
||||||
//Count used licenses
|
//Count used licenses
|
||||||
console.debug(`metrics/compute/${login}/plugins > licenses > computing ratio`)
|
console.debug(`metrics/compute/${login}/plugins > licenses > computing ratio`)
|
||||||
const total = Object.values(used).reduce((a, b) => a + b, 0)
|
const total = Object.values(used).reduce((a, b) => a + b, 0)
|
||||||
//Format used licenses and compute positions
|
//Format used licenses and compute positions
|
||||||
const list = Object.entries(used).map(([key, count]) => ({name:licenses[key]?.spdxId ?? `${key.charAt(0).toLocaleUpperCase()}${key.substring(1)}`, key, count, value:count / total, x:0, color:licenses[key]?.color ?? "#6e7681", order:licenses[key]?.order ?? -1})).sort((
|
const list = Object.entries(used).map(([key, count]) => ({name: licenses[key]?.spdxId ?? `${key.charAt(0).toLocaleUpperCase()}${key.substring(1)}`, key, count, value: count / total, x: 0, color: licenses[key]?.color ?? "#6e7681", order: licenses[key]?.order ?? -1})).sort((
|
||||||
a,
|
a,
|
||||||
b,
|
b,
|
||||||
) => a.order === b.order ? b.count - a.count : b.order - a.order)
|
) => a.order === b.order ? b.count - a.count : b.order - a.order)
|
||||||
@@ -111,7 +113,7 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,24 +11,25 @@ export default async function({login, data, imports, rest, q, account}, {enabled
|
|||||||
skipped.push(...data.shared["repositories.skipped"])
|
skipped.push(...data.shared["repositories.skipped"])
|
||||||
|
|
||||||
//Context
|
//Context
|
||||||
let context = {mode:"user"}
|
let context = {mode: "user"}
|
||||||
if (q.repo) {
|
if (q.repo) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
|
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
|
||||||
context = {...context, mode:"repository"}
|
context = {...context, mode: "repository"}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Repositories
|
//Repositories
|
||||||
const repositories = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})) ?? []
|
const repositories = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})) ?? []
|
||||||
|
|
||||||
//Get contributors stats from repositories
|
//Get contributors stats from repositories
|
||||||
console.debug(`metrics/compute/${login}/plugins > lines > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > lines > querying api`)
|
||||||
const lines = {added:0, deleted:0, changed:0}
|
const lines = {added: 0, deleted: 0, changed: 0}
|
||||||
const response = [...await Promise.allSettled(repositories.map(({repo, owner}) => (skipped.includes(repo.toLocaleLowerCase())) || (skipped.includes(`${owner}/${repo}`.toLocaleLowerCase())) ? {} : rest.repos.getContributorsStats({owner, repo})))].filter(({status}) => status === "fulfilled"
|
const response = [...await Promise.allSettled(repositories.map(({repo, owner}) => (skipped.includes(repo.toLocaleLowerCase())) || (skipped.includes(`${owner}/${repo}`.toLocaleLowerCase())) ? {} : rest.repos.getContributorsStats({owner, repo})))].filter(({status}) =>
|
||||||
|
status === "fulfilled"
|
||||||
).map(({value}) => value)
|
).map(({value}) => value)
|
||||||
|
|
||||||
//Compute changed lines
|
//Compute changed lines
|
||||||
console.debug(`metrics/compute/${login}/plugins > lines > computing total diff`)
|
console.debug(`metrics/compute/${login}/plugins > lines > computing total diff`)
|
||||||
response.map(({data:repository}) => {
|
response.map(({data: repository}) => {
|
||||||
//Check if data are available
|
//Check if data are available
|
||||||
if (!Array.isArray(repository))
|
if (!Array.isArray(repository))
|
||||||
return
|
return
|
||||||
@@ -43,6 +44,6 @@ export default async function({login, data, imports, rest, q, account}, {enabled
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,28 +3,28 @@ import crypto from "crypto"
|
|||||||
|
|
||||||
//Supported providers
|
//Supported providers
|
||||||
const providers = {
|
const providers = {
|
||||||
apple:{
|
apple: {
|
||||||
name:"Apple Music",
|
name: "Apple Music",
|
||||||
embed:/^https:..embed.music.apple.com.\w+.playlist/,
|
embed: /^https:..embed.music.apple.com.\w+.playlist/,
|
||||||
},
|
},
|
||||||
spotify:{
|
spotify: {
|
||||||
name:"Spotify",
|
name: "Spotify",
|
||||||
embed:/^https:..open.spotify.com.embed.playlist/,
|
embed: /^https:..open.spotify.com.embed.playlist/,
|
||||||
},
|
},
|
||||||
lastfm:{
|
lastfm: {
|
||||||
name:"Last.fm",
|
name: "Last.fm",
|
||||||
embed:/^\b$/,
|
embed: /^\b$/,
|
||||||
},
|
},
|
||||||
youtube:{
|
youtube: {
|
||||||
name:"YouTube Music",
|
name: "YouTube Music",
|
||||||
embed:/^https:..music.youtube.com.playlist/,
|
embed: /^https:..music.youtube.com.playlist/,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
//Supported modes
|
//Supported modes
|
||||||
const modes = {
|
const modes = {
|
||||||
playlist:"Suggested tracks",
|
playlist: "Suggested tracks",
|
||||||
recent:"Recently played",
|
recent: "Recently played",
|
||||||
top:"Top played",
|
top: "Top played",
|
||||||
}
|
}
|
||||||
|
|
||||||
//Setup
|
//Setup
|
||||||
@@ -47,7 +47,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
let tracks = null
|
let tracks = null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {provider, mode, playlist, limit, user, "played.at":played_at, "time.range":time_range, "top.type":top_type, token:_token} = imports.metadata.plugins.music.inputs({data, account, q})
|
let {provider, mode, playlist, limit, user, "played.at": played_at, "time.range": time_range, "top.type": top_type, token: _token} = imports.metadata.plugins.music.inputs({data, account, q})
|
||||||
if ((sandbox) && (_token)) {
|
if ((sandbox) && (_token)) {
|
||||||
token = _token
|
token = _token
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > overriden token value through user inputs as sandbox mode is enabled`)
|
console.debug(`metrics/compute/${login}/plugins > music > overriden token value through user inputs as sandbox mode is enabled`)
|
||||||
@@ -71,16 +71,16 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Provider
|
//Provider
|
||||||
if (!(provider in providers))
|
if (!(provider in providers))
|
||||||
throw {error:{message:provider ? `Unsupported provider "${provider}"` : "Missing provider"}, ...raw}
|
throw {error: {message: provider ? `Unsupported provider "${provider}"` : "Missing provider"}, ...raw}
|
||||||
//Mode
|
//Mode
|
||||||
if (!(mode in modes))
|
if (!(mode in modes))
|
||||||
throw {error:{message:`Unsupported mode "${mode}"`}, ...raw}
|
throw {error: {message: `Unsupported mode "${mode}"`}, ...raw}
|
||||||
//Playlist mode
|
//Playlist mode
|
||||||
if (mode === "playlist") {
|
if (mode === "playlist") {
|
||||||
if (!playlist)
|
if (!playlist)
|
||||||
throw {error:{message:"Missing playlist url"}, ...raw}
|
throw {error: {message: "Missing playlist url"}, ...raw}
|
||||||
if (!providers[provider].embed.test(playlist))
|
if (!providers[provider].embed.test(playlist))
|
||||||
throw {error:{message:"Unsupported playlist url format"}, ...raw}
|
throw {error: {message: "Unsupported playlist url format"}, ...raw}
|
||||||
}
|
}
|
||||||
//Limit
|
//Limit
|
||||||
limit = Math.max(1, Math.min(100, Number(limit)))
|
limit = Math.max(1, Math.min(100, Number(limit)))
|
||||||
@@ -111,9 +111,9 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
...await frame.evaluate(() => {
|
...await frame.evaluate(() => {
|
||||||
const tracklist = document.querySelector("embed-root").shadowRoot.querySelector(".audio-tracklist")
|
const tracklist = document.querySelector("embed-root").shadowRoot.querySelector(".audio-tracklist")
|
||||||
return [...tracklist.querySelectorAll("embed-audio-tracklist-item")].map(item => ({
|
return [...tracklist.querySelectorAll("embed-audio-tracklist-item")].map(item => ({
|
||||||
name:item.querySelector(".audio-tracklist-item__metadata h3").innerText,
|
name: item.querySelector(".audio-tracklist-item__metadata h3").innerText,
|
||||||
artist:item.querySelector(".audio-tracklist-item__metadata h4").innerText,
|
artist: item.querySelector(".audio-tracklist-item__metadata h4").innerText,
|
||||||
artwork:item.querySelector("apple-music-artwork")?.shadowRoot?.querySelector("picture source")?.srcset?.split(",")?.[0]?.replace(/\s+\d+x$/, ""),
|
artwork: item.querySelector("apple-music-artwork")?.shadowRoot?.querySelector("picture source")?.srcset?.split(",")?.[0]?.replace(/\s+\d+x$/, ""),
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
@@ -124,11 +124,12 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Parse tracklist
|
//Parse tracklist
|
||||||
await frame.waitForSelector("table")
|
await frame.waitForSelector("table")
|
||||||
tracks = [
|
tracks = [
|
||||||
...await frame.evaluate(() => [...document.querySelectorAll("table tr")].map(tr => ({
|
...await frame.evaluate(() =>
|
||||||
name:tr.querySelector("td:nth-child(2) div div:nth-child(1)").innerText,
|
[...document.querySelectorAll("table tr")].map(tr => ({
|
||||||
artist:tr.querySelector("td:nth-child(2) div div:nth-child(2)").innerText,
|
name: tr.querySelector("td:nth-child(2) div div:nth-child(1)").innerText,
|
||||||
|
artist: tr.querySelector("td:nth-child(2) div div:nth-child(2)").innerText,
|
||||||
//Spotify doesn't provide artworks so we fallback on playlist artwork instead
|
//Spotify doesn't provide artworks so we fallback on playlist artwork instead
|
||||||
artwork:window.getComputedStyle(document.querySelector("button[title=Play]")?.parentNode ?? document.querySelector("button").parentNode, null).backgroundImage.match(/^url\("(?<url>https:...+)"\)$/)?.groups?.url ?? null,
|
artwork: window.getComputedStyle(document.querySelector("button[title=Play]")?.parentNode ?? document.querySelector("button").parentNode, null).backgroundImage.match(/^url\("(?<url>https:...+)"\)$/)?.groups?.url ?? null,
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -140,10 +141,11 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
await frame.evaluate(() => window.scrollBy(0, window.innerHeight))
|
await frame.evaluate(() => window.scrollBy(0, window.innerHeight))
|
||||||
//Parse tracklist
|
//Parse tracklist
|
||||||
tracks = [
|
tracks = [
|
||||||
...await frame.evaluate(() => [...document.querySelectorAll("ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer")].map(item => ({
|
...await frame.evaluate(() =>
|
||||||
name:item.querySelector("yt-formatted-string.title > a")?.innerText ?? "",
|
[...document.querySelectorAll("ytmusic-playlist-shelf-renderer ytmusic-responsive-list-item-renderer")].map(item => ({
|
||||||
artist:item.querySelector(".secondary-flex-columns > yt-formatted-string > a")?.innerText ?? "",
|
name: item.querySelector("yt-formatted-string.title > a")?.innerText ?? "",
|
||||||
artwork:item.querySelector("img").src,
|
artist: item.querySelector(".secondary-flex-columns > yt-formatted-string > a")?.innerText ?? "",
|
||||||
|
artwork: item.querySelector("img").src,
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -151,7 +153,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Unsupported
|
//Unsupported
|
||||||
default:
|
default:
|
||||||
throw {error:{message:`Unsupported mode "${mode}" for provider "${provider}"`}, ...raw}
|
throw {error: {message: `Unsupported mode "${mode}" for provider "${provider}"`}, ...raw}
|
||||||
}
|
}
|
||||||
//Close browser
|
//Close browser
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > closing browser`)
|
console.debug(`metrics/compute/${login}/plugins > music > closing browser`)
|
||||||
@@ -160,7 +162,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
if (Array.isArray(tracks)) {
|
if (Array.isArray(tracks)) {
|
||||||
//Tracks
|
//Tracks
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > found ${tracks.length} tracks`)
|
console.debug(`metrics/compute/${login}/plugins > music > found ${tracks.length} tracks`)
|
||||||
console.debug(imports.util.inspect(tracks, {depth:Infinity, maxStringLength:256}))
|
console.debug(imports.util.inspect(tracks, {depth: Infinity, maxStringLength: 256}))
|
||||||
//Shuffle tracks
|
//Shuffle tracks
|
||||||
tracks = imports.shuffle(tracks)
|
tracks = imports.shuffle(tracks)
|
||||||
}
|
}
|
||||||
@@ -175,14 +177,14 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Prepare credentials
|
//Prepare credentials
|
||||||
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
||||||
if ((!client_id) || (!client_secret) || (!refresh_token))
|
if ((!client_id) || (!client_secret) || (!refresh_token))
|
||||||
throw {error:{message:"Spotify token must contain client id/secret and refresh token"}}
|
throw {error: {message: "Spotify token must contain client id/secret and refresh token"}}
|
||||||
//API call and parse tracklist
|
//API call and parse tracklist
|
||||||
try {
|
try {
|
||||||
//Request access token
|
//Request access token
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`)
|
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`)
|
||||||
const {data:{access_token:access}} = await imports.axios.post("https://accounts.spotify.com/api/token", `${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`, {
|
const {data: {access_token: access}} = await imports.axios.post("https://accounts.spotify.com/api/token", `${new imports.url.URLSearchParams({grant_type: "refresh_token", refresh_token, client_id, client_secret})}`, {
|
||||||
headers:{
|
headers: {
|
||||||
"Content-Type":"application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > got access token`)
|
console.debug(`metrics/compute/${login}/plugins > music > got access token`)
|
||||||
@@ -193,16 +195,16 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Load track half-hour by half-hour
|
//Load track half-hour by half-hour
|
||||||
const timestamp = Date.now() - hours * 60 * 60 * 1000
|
const timestamp = Date.now() - hours * 60 * 60 * 1000
|
||||||
const loaded = (await imports.axios.get(`https://api.spotify.com/v1/me/player/recently-played?after=${timestamp}`, {
|
const loaded = (await imports.axios.get(`https://api.spotify.com/v1/me/player/recently-played?after=${timestamp}`, {
|
||||||
headers:{
|
headers: {
|
||||||
"Content-Type":"application/json",
|
"Content-Type": "application/json",
|
||||||
Accept:"application/json",
|
Accept: "application/json",
|
||||||
Authorization:`Bearer ${access}`,
|
Authorization: `Bearer ${access}`,
|
||||||
},
|
},
|
||||||
})).data.items.map(({track, played_at}) => ({
|
})).data.items.map(({track, played_at}) => ({
|
||||||
name:track.name,
|
name: track.name,
|
||||||
artist:track.artists[0].name,
|
artist: track.artists[0].name,
|
||||||
artwork:track.album.images[0].url,
|
artwork: track.album.images[0].url,
|
||||||
played_at:played_at ? `${imports.format.date(played_at, {time:true})} on ${imports.format.date(played_at, {date:true})}` : null,
|
played_at: played_at ? `${imports.format.date(played_at, {time: true})} on ${imports.format.date(played_at, {date: true})}` : null,
|
||||||
}))
|
}))
|
||||||
//Ensure no duplicate are added
|
//Ensure no duplicate are added
|
||||||
for (const track of loaded) {
|
for (const track of loaded) {
|
||||||
@@ -221,7 +223,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
const description = error.response.data?.error_description ?? null
|
const description = error.response.data?.error_description ?? null
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
throw {error:{message, instance:error}, ...raw}
|
throw {error: {message, instance: error}, ...raw}
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -233,14 +235,14 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
try {
|
try {
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > querying lastfm api`)
|
console.debug(`metrics/compute/${login}/plugins > music > querying lastfm api`)
|
||||||
tracks = (await imports.axios.get(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${user}&api_key=${token}&limit=${limit}&format=json`, {
|
tracks = (await imports.axios.get(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${user}&api_key=${token}&limit=${limit}&format=json`, {
|
||||||
headers:{
|
headers: {
|
||||||
"User-Agent":"lowlighter/metrics",
|
"User-Agent": "lowlighter/metrics",
|
||||||
Accept:"application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
})).data.recenttracks.track.map(track => ({
|
})).data.recenttracks.track.map(track => ({
|
||||||
name:track.name,
|
name: track.name,
|
||||||
artist:track.artist["#text"],
|
artist: track.artist["#text"],
|
||||||
artwork:track.image.reverse()[0]["#text"],
|
artwork: track.image.reverse()[0]["#text"],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
@@ -250,7 +252,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
const description = error.response.data?.message ?? null
|
const description = error.response.data?.message ?? null
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
throw {error:{message, instance:error}, ...raw}
|
throw {error: {message, instance: error}, ...raw}
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -267,25 +269,25 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Request access token
|
//Request access token
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with youtube refresh token`)
|
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with youtube refresh token`)
|
||||||
const res = await imports.axios.post("https://music.youtube.com/youtubei/v1/browse?alt=json&key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30", {
|
const res = await imports.axios.post("https://music.youtube.com/youtubei/v1/browse?alt=json&key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30", {
|
||||||
browseEndpointContextSupportedConfigs:{
|
browseEndpointContextSupportedConfigs: {
|
||||||
browseEndpointContextMusicConfig:{
|
browseEndpointContextMusicConfig: {
|
||||||
pageType:"MUSIC_PAGE_TYPE_PLAYLIST",
|
pageType: "MUSIC_PAGE_TYPE_PLAYLIST",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
context:{
|
context: {
|
||||||
client:{
|
client: {
|
||||||
clientName:"WEB_REMIX",
|
clientName: "WEB_REMIX",
|
||||||
clientVersion:"1.20211129.00.01",
|
clientVersion: "1.20211129.00.01",
|
||||||
gl:"US",
|
gl: "US",
|
||||||
hl:"en",
|
hl: "en",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
browseId:"FEmusic_history",
|
browseId: "FEmusic_history",
|
||||||
}, {
|
}, {
|
||||||
headers:{
|
headers: {
|
||||||
Authorization:SAPISIDHASH,
|
Authorization: SAPISIDHASH,
|
||||||
Cookie:token,
|
Cookie: token,
|
||||||
"x-origin":"https://music.youtube.com",
|
"x-origin": "https://music.youtube.com",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
//Retrieve tracks
|
//Retrieve tracks
|
||||||
@@ -296,9 +298,9 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
for (let i = 0; i < parsedHistory.length; i++) {
|
for (let i = 0; i < parsedHistory.length; i++) {
|
||||||
let track = parsedHistory[i]
|
let track = parsedHistory[i]
|
||||||
tracks.push({
|
tracks.push({
|
||||||
name:track.flexColumns[0].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text,
|
name: track.flexColumns[0].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text,
|
||||||
artist:track.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text,
|
artist: track.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text,
|
||||||
artwork:track.thumbnail.musicThumbnailRenderer.thumbnail.thumbnails[0].url,
|
artwork: track.thumbnail.musicThumbnailRenderer.thumbnail.thumbnails[0].url,
|
||||||
})
|
})
|
||||||
//Early break
|
//Early break
|
||||||
if (tracks.length >= limit)
|
if (tracks.length >= limit)
|
||||||
@@ -312,7 +314,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
const description = error.response.data?.error_description ?? null
|
const description = error.response.data?.error_description ?? null
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
throw {error:{message, instance:error}, ...raw}
|
throw {error: {message, instance: error}, ...raw}
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -320,7 +322,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Unsupported
|
//Unsupported
|
||||||
default:
|
default:
|
||||||
throw {error:{message:`Unsupported mode "${mode}" for provider "${provider}"`}, ...raw}
|
throw {error: {message: `Unsupported mode "${mode}" for provider "${provider}"`}, ...raw}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -337,7 +339,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
time_msg = "overall"
|
time_msg = "overall"
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw {error:{message:`Unsupported time range "${time_range}"`}, ...raw}
|
throw {error: {message: `Unsupported time range "${time_range}"`}, ...raw}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top_type === "artists") {
|
if (top_type === "artists") {
|
||||||
@@ -362,17 +364,17 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
//Prepare credentials
|
//Prepare credentials
|
||||||
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
const [client_id, client_secret, refresh_token] = token.split(",").map(part => part.trim())
|
||||||
if ((!client_id) || (!client_secret) || (!refresh_token))
|
if ((!client_id) || (!client_secret) || (!refresh_token))
|
||||||
throw {error:{message:"Spotify token must contain client id/secret and refresh token"}}
|
throw {error: {message: "Spotify token must contain client id/secret and refresh token"}}
|
||||||
else if (limit > 50)
|
else if (limit > 50)
|
||||||
throw {error:{message:"Spotify top limit cannot be greater than 50"}}
|
throw {error: {message: "Spotify top limit cannot be greater than 50"}}
|
||||||
|
|
||||||
//API call and parse tracklist
|
//API call and parse tracklist
|
||||||
try {
|
try {
|
||||||
//Request access token
|
//Request access token
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`)
|
console.debug(`metrics/compute/${login}/plugins > music > requesting access token with spotify refresh token`)
|
||||||
const {data:{access_token:access}} = await imports.axios.post("https://accounts.spotify.com/api/token", `${new imports.url.URLSearchParams({grant_type:"refresh_token", refresh_token, client_id, client_secret})}`, {
|
const {data: {access_token: access}} = await imports.axios.post("https://accounts.spotify.com/api/token", `${new imports.url.URLSearchParams({grant_type: "refresh_token", refresh_token, client_id, client_secret})}`, {
|
||||||
headers:{
|
headers: {
|
||||||
"Content-Type":"application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
console.debug(`metrics/compute/${login}/plugins > music > got access token`)
|
console.debug(`metrics/compute/${login}/plugins > music > got access token`)
|
||||||
@@ -384,33 +386,33 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
await imports.axios.get(
|
await imports.axios.get(
|
||||||
`https://api.spotify.com/v1/me/top/artists?time_range=${time_range}_term&limit=${limit}`,
|
`https://api.spotify.com/v1/me/top/artists?time_range=${time_range}_term&limit=${limit}`,
|
||||||
{
|
{
|
||||||
headers:{
|
headers: {
|
||||||
"Content-Type":"application/json",
|
"Content-Type": "application/json",
|
||||||
Accept:"application/json",
|
Accept: "application/json",
|
||||||
Authorization:`Bearer ${access}`,
|
Authorization: `Bearer ${access}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
).data.items.map(({name, genres, images}) => ({
|
).data.items.map(({name, genres, images}) => ({
|
||||||
name,
|
name,
|
||||||
artist:genres.join(" • "),
|
artist: genres.join(" • "),
|
||||||
artwork:images[0].url,
|
artwork: images[0].url,
|
||||||
}))
|
}))
|
||||||
: (
|
: (
|
||||||
await imports.axios.get(
|
await imports.axios.get(
|
||||||
`https://api.spotify.com/v1/me/top/tracks?time_range=${time_range}_term&limit=${limit}`,
|
`https://api.spotify.com/v1/me/top/tracks?time_range=${time_range}_term&limit=${limit}`,
|
||||||
{
|
{
|
||||||
headers:{
|
headers: {
|
||||||
"Content-Type":"application/json",
|
"Content-Type": "application/json",
|
||||||
Accept:"application/json",
|
Accept: "application/json",
|
||||||
Authorization:`Bearer ${access}`,
|
Authorization: `Bearer ${access}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
).data.items.map(({name, artists, album}) => ({
|
).data.items.map(({name, artists, album}) => ({
|
||||||
name,
|
name,
|
||||||
artist:artists[0].name,
|
artist: artists[0].name,
|
||||||
artwork:album.images[0].url,
|
artwork: album.images[0].url,
|
||||||
}))
|
}))
|
||||||
//Ensure no duplicate are added
|
//Ensure no duplicate are added
|
||||||
for (const track of loaded) {
|
for (const track of loaded) {
|
||||||
@@ -425,7 +427,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
const description = error.response.data?.error_description ?? null
|
const description = error.response.data?.error_description ?? null
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
throw {error:{message, instance:error}, ...raw}
|
throw {error: {message, instance: error}, ...raw}
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -442,31 +444,31 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
await imports.axios.get(
|
await imports.axios.get(
|
||||||
`https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${user}&api_key=${token}&limit=${limit}&period=${period}&format=json`,
|
`https://ws.audioscrobbler.com/2.0/?method=user.gettopartists&user=${user}&api_key=${token}&limit=${limit}&period=${period}&format=json`,
|
||||||
{
|
{
|
||||||
headers:{
|
headers: {
|
||||||
"User-Agent":"lowlighter/metrics",
|
"User-Agent": "lowlighter/metrics",
|
||||||
Accept:"application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
).data.topartists.artist.map(artist => ({
|
).data.topartists.artist.map(artist => ({
|
||||||
name:artist.name,
|
name: artist.name,
|
||||||
artist:`Play count: ${artist.playcount}`,
|
artist: `Play count: ${artist.playcount}`,
|
||||||
artwork:artist.image.reverse()[0]["#text"],
|
artwork: artist.image.reverse()[0]["#text"],
|
||||||
}))
|
}))
|
||||||
: (
|
: (
|
||||||
await imports.axios.get(
|
await imports.axios.get(
|
||||||
`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${user}&api_key=${token}&limit=${limit}&period=${period}&format=json`,
|
`https://ws.audioscrobbler.com/2.0/?method=user.gettoptracks&user=${user}&api_key=${token}&limit=${limit}&period=${period}&format=json`,
|
||||||
{
|
{
|
||||||
headers:{
|
headers: {
|
||||||
"User-Agent":"lowlighter/metrics",
|
"User-Agent": "lowlighter/metrics",
|
||||||
Accept:"application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
).data.toptracks.track.map(track => ({
|
).data.toptracks.track.map(track => ({
|
||||||
name:track.name,
|
name: track.name,
|
||||||
artist:track.artist.name,
|
artist: track.artist.name,
|
||||||
artwork:track.image.reverse()[0]["#text"],
|
artwork: track.image.reverse()[0]["#text"],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
@@ -476,7 +478,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
const description = error.response.data?.message ?? null
|
const description = error.response.data?.message ?? null
|
||||||
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
const message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
throw {error:{message, instance:error}, ...raw}
|
throw {error: {message, instance: error}, ...raw}
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -484,13 +486,13 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Unsupported
|
//Unsupported
|
||||||
default:
|
default:
|
||||||
throw {error:{message:`Unsupported mode "${mode}" for provider "${provider}"`}, ...raw}
|
throw {error: {message: `Unsupported mode "${mode}" for provider "${provider}"`}, ...raw}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//Unsupported
|
//Unsupported
|
||||||
default:
|
default:
|
||||||
throw {error:{message:`Unsupported mode "${mode}"`}, ...raw}
|
throw {error: {message: `Unsupported mode "${mode}"`}, ...raw}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Format tracks
|
//Format tracks
|
||||||
@@ -511,13 +513,13 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Unhandled error
|
//Unhandled error
|
||||||
throw {error:{message:"An error occured (could not retrieve tracks)"}}
|
throw {error: {message: "An error occured (could not retrieve tracks)"}}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,19 +17,20 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > retrieving contributed repositories after ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > notable > retrieving contributed repositories after ${cursor}`)
|
||||||
const {user:{repositoriesContributedTo:{edges}}} = await graphql(queries.notable.contributions({login, types:types.map(x => x.toLocaleUpperCase()).join(", "), after:cursor ? `after: "${cursor}"` : "", repositories:data.shared["repositories.batch"] || 100}))
|
const {user: {repositoriesContributedTo: {edges}}} = await graphql(queries.notable.contributions({login, types: types.map(x => x.toLocaleUpperCase()).join(", "), after: cursor ? `after: "${cursor}"` : "", repositories: data.shared["repositories.batch"] || 100}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
edges
|
edges
|
||||||
.filter(({node}) => !((skipped.includes(node.nameWithOwner.toLocaleLowerCase())) || (skipped.includes(node.nameWithOwner.split("/")[1].toLocaleLowerCase()))))
|
.filter(({node}) => !((skipped.includes(node.nameWithOwner.toLocaleLowerCase())) || (skipped.includes(node.nameWithOwner.split("/")[1].toLocaleLowerCase()))))
|
||||||
.filter(({node}) => ({all:true, organization:node.isInOrganization, user:!node.isInOrganization}[from]))
|
.filter(({node}) => ({all: true, organization: node.isInOrganization, user: !node.isInOrganization}[from]))
|
||||||
.filter(({node}) => imports.ghfilter(filter, {name:node.nameWithOwner, user:node.owner.login, stars:node.stargazers.totalCount, watchers:node.watchers.totalCount, forks:node.forks.totalCount}))
|
.filter(({node}) => imports.ghfilter(filter, {name: node.nameWithOwner, user: node.owner.login, stars: node.stargazers.totalCount, watchers: node.watchers.totalCount, forks: node.forks.totalCount}))
|
||||||
.map(({node}) => contributions.push({handle:node.nameWithOwner, stars:node.stargazers.totalCount, issues:node.issues.totalCount, pulls:node.pullRequests.totalCount, organization:node.isInOrganization, avatarUrl:node.owner.avatarUrl}))
|
.map(({node}) => contributions.push({handle: node.nameWithOwner, stars: node.stargazers.totalCount, issues: node.issues.totalCount, pulls: node.pullRequests.totalCount, organization: node.isInOrganization, avatarUrl: node.owner.avatarUrl}))
|
||||||
pushed = edges.length
|
pushed = edges.length
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set contributions
|
//Set contributions
|
||||||
contributions = (await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name:handle.split("/").shift(), handle, stars, issues, pulls, avatar:await imports.imgb64(avatarUrl), organization})))).sort((a, b) => a.name.localeCompare(b.name)
|
contributions = (await Promise.all(contributions.map(async ({handle, stars, issues, pulls, avatarUrl, organization}) => ({name: handle.split("/").shift(), handle, stars, issues, pulls, avatar: await imports.imgb64(avatarUrl), organization})))).sort((a, b) =>
|
||||||
|
a.name.localeCompare(b.name)
|
||||||
)
|
)
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
|
console.debug(`metrics/compute/${login}/plugins > notable > found ${contributions.length} notable contributions`)
|
||||||
|
|
||||||
@@ -46,9 +47,9 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > retrieving user issues after ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > notable > retrieving user issues after ${cursor}`)
|
||||||
const {user:{issues:{edges}}} = await graphql(queries.notable.issues({login, type:"issues", after:cursor ? `after: "${cursor}"` : ""}))
|
const {user: {issues: {edges}}} = await graphql(queries.notable.issues({login, type: "issues", after: cursor ? `after: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
edges.map(({node:{repository:{nameWithOwner:repository}}}) => issues[repository] = (issues[repositories] ?? 0) + 1)
|
edges.map(({node: {repository: {nameWithOwner: repository}}}) => issues[repository] = (issues[repositories] ?? 0) + 1)
|
||||||
pushed = edges.length
|
pushed = edges.length
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
}
|
}
|
||||||
@@ -60,9 +61,9 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > notable > retrieving user pull requests after ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > notable > retrieving user pull requests after ${cursor}`)
|
||||||
const {user:{pullRequests:{edges}}} = await graphql(queries.notable.issues({login, type:"pullRequests", after:cursor ? `after: "${cursor}"` : ""}))
|
const {user: {pullRequests: {edges}}} = await graphql(queries.notable.issues({login, type: "pullRequests", after: cursor ? `after: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
edges.map(({node:{repository:{nameWithOwner:repository}}}) => pulls[repository] = (pulls[repositories] ?? 0) + 1)
|
edges.map(({node: {repository: {nameWithOwner: repository}}}) => pulls[repository] = (pulls[repositories] ?? 0) + 1)
|
||||||
pushed = edges.length
|
pushed = edges.length
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
}
|
}
|
||||||
@@ -74,24 +75,24 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
const [owner, repo] = handle.split("/")
|
const [owner, repo] = handle.split("/")
|
||||||
try {
|
try {
|
||||||
//Count total commits on repository
|
//Count total commits on repository
|
||||||
const {repository:{defaultBranchRef:{target:{history}}}} = await graphql(queries.notable.commits({owner, repo}))
|
const {repository: {defaultBranchRef: {target: {history}}}} = await graphql(queries.notable.commits({owner, repo}))
|
||||||
contribution.history = history.totalCount
|
contribution.history = history.totalCount
|
||||||
|
|
||||||
//Load maintainers (errors probably means that token is not allowed to list contributors hence not a maintainer of said repo)
|
//Load maintainers (errors probably means that token is not allowed to list contributors hence not a maintainer of said repo)
|
||||||
const {data:collaborators} = await rest.repos.listCollaborators({owner, repo}).catch(() => ({data:[]}))
|
const {data: collaborators} = await rest.repos.listCollaborators({owner, repo}).catch(() => ({data: []}))
|
||||||
const maintainers = collaborators.filter(({role_name:role}) => ["admin", "maintain", "write"].includes(role)).map(({login}) => login)
|
const maintainers = collaborators.filter(({role_name: role}) => ["admin", "maintain", "write"].includes(role)).map(({login}) => login)
|
||||||
|
|
||||||
//Count total commits of user
|
//Count total commits of user
|
||||||
const {data:contributions = []} = await rest.repos.getContributorsStats({owner, repo})
|
const {data: contributions = []} = await rest.repos.getContributorsStats({owner, repo})
|
||||||
const commits = contributions.filter(({author}) => author.login.toLocaleLowerCase() === login.toLocaleLowerCase()).reduce((a, {total:b}) => a + b, 0)
|
const commits = contributions.filter(({author}) => author.login.toLocaleLowerCase() === login.toLocaleLowerCase()).reduce((a, {total: b}) => a + b, 0)
|
||||||
|
|
||||||
//Save user data
|
//Save user data
|
||||||
contribution.user = {
|
contribution.user = {
|
||||||
commits,
|
commits,
|
||||||
percentage:commits / contribution.history,
|
percentage: commits / contribution.history,
|
||||||
maintainer:maintainers.includes(login),
|
maintainer: maintainers.includes(login),
|
||||||
issues:issues[handle] ?? 0,
|
issues: issues[handle] ?? 0,
|
||||||
pulls:pulls[handle] ?? 0,
|
pulls: pulls[handle] ?? 0,
|
||||||
get stars() {
|
get stars() {
|
||||||
return Math.round(this.maintainer ? stars : this.percentage * stars)
|
return Math.round(this.maintainer ? stars : this.percentage * stars)
|
||||||
},
|
},
|
||||||
@@ -115,7 +116,7 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
const aggregate = aggregated.get(key)
|
const aggregate = aggregated.get(key)
|
||||||
aggregate.aggregated++
|
aggregate.aggregated++
|
||||||
if (indepth) {
|
if (indepth) {
|
||||||
const {history = 0, user:{commits = 0, percentage = 0, maintainer = false} = {}} = _extras
|
const {history = 0, user: {commits = 0, percentage = 0, maintainer = false} = {}} = _extras
|
||||||
aggregate.history = aggregate.history ?? 0
|
aggregate.history = aggregate.history ?? 0
|
||||||
aggregate.history += history
|
aggregate.history += history
|
||||||
aggregate.user = aggregate.user ?? {}
|
aggregate.user = aggregate.user ?? {}
|
||||||
@@ -124,16 +125,16 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
aggregate.user.maintainer = aggregate.user.maintainer || maintainer
|
aggregate.user.maintainer = aggregate.user.maintainer || maintainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
aggregated.set(key, {name:key, handle, avatar, organization, stars, aggregated:1, ..._extras})
|
aggregated.set(key, {name: key, handle, avatar, organization, stars, aggregated: 1, ..._extras})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
contributions = [...aggregated.values()]
|
contributions = [...aggregated.values()]
|
||||||
if (indepth) {
|
if (indepth) {
|
||||||
//Normalize contribution percentage
|
//Normalize contribution percentage
|
||||||
contributions.map(aggregate => aggregate.user ? aggregate.user.percentage /= aggregate.aggregated : null)
|
contributions.map(aggregate => aggregate.user ? aggregate.user.percentage /= aggregate.aggregated : null)
|
||||||
//Additional filtering (no user commits means that API wasn't able to answer back, considering it as matching by default)
|
//Additional filtering (no user commits means that API wasn't able to answer back, considering it as matching by default)
|
||||||
contributions = contributions.filter(({handle, user}) => !user?.commits ? true : imports.ghfilter(filter, {handle, commits:contributions.history, "commits.user":user.commits, "commits.user%":user.percentage * 100, maintainer:user.maintainer}))
|
contributions = contributions.filter(({handle, user}) => !user?.commits ? true : imports.ghfilter(filter, {handle, commits: contributions.history, "commits.user": user.commits, "commits.user%": user.percentage * 100, maintainer: user.maintainer}))
|
||||||
//Sort contribution by maintainer first and then by contribution percentage
|
//Sort contribution by maintainer first and then by contribution percentage
|
||||||
contributions = contributions.sort((a, b) => ((b.user?.percentage + b.user?.maintainer) || 0) - ((a.user?.percentage + a.user?.maintainer) || 0))
|
contributions = contributions.sort((a, b) => ((b.user?.percentage + b.user?.maintainer) || 0) - ((a.user?.percentage + a.user?.maintainer) || 0))
|
||||||
}
|
}
|
||||||
@@ -143,6 +144,6 @@ export default async function({login, q, imports, rest, graphql, data, account,
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
if (!/^https?:[/][/]/.test(url))
|
if (!/^https?:[/][/]/.test(url))
|
||||||
url = `https://${url}`
|
url = `https://${url}`
|
||||||
const {protocol, host} = imports.url.parse(url)
|
const {protocol, host} = imports.url.parse(url)
|
||||||
const result = {url:`${protocol}//${host}`, detailed, scores:[], metrics:{}}
|
const result = {url: `${protocol}//${host}`, detailed, scores: [], metrics: {}}
|
||||||
//Load scores from API
|
//Load scores from API
|
||||||
console.debug(`metrics/compute/${login}/plugins > pagespeed > querying api for ${result.url}`)
|
console.debug(`metrics/compute/${login}/plugins > pagespeed > querying api for ${result.url}`)
|
||||||
const scores = new Map()
|
const scores = new Map()
|
||||||
@@ -53,6 +53,6 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
}
|
}
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,20 @@ export default async function({login, data, graphql, rest, q, queries, imports,
|
|||||||
|
|
||||||
//Context
|
//Context
|
||||||
let context = {
|
let context = {
|
||||||
mode:"user",
|
mode: "user",
|
||||||
types:account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "membersWithRole", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"],
|
types: account === "organization" ? ["sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "membersWithRole", "thanks"] : ["followers", "following", "sponsorshipsAsMaintainer", "sponsorshipsAsSponsor", "thanks"],
|
||||||
default:"followers, following",
|
default: "followers, following",
|
||||||
alias:{followed:"following", sponsors:"sponsorshipsAsMaintainer", sponsored:"sponsorshipsAsSponsor", sponsoring:"sponsorshipsAsSponsor", members:"membersWithRole"},
|
alias: {followed: "following", sponsors: "sponsorshipsAsMaintainer", sponsored: "sponsorshipsAsSponsor", sponsoring: "sponsorshipsAsSponsor", members: "membersWithRole"},
|
||||||
sponsorships:{sponsorshipsAsMaintainer:"sponsorEntity", sponsorshipsAsSponsor:"sponsorable"},
|
sponsorships: {sponsorshipsAsMaintainer: "sponsorEntity", sponsorshipsAsSponsor: "sponsorable"},
|
||||||
}
|
}
|
||||||
if (q.repo) {
|
if (q.repo) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
|
console.debug(`metrics/compute/${login}/plugins > people > switched to repository mode`)
|
||||||
const {owner, repo} = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})).shift()
|
const {owner, repo} = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})).shift()
|
||||||
context = {...context, mode:"repository", types:["contributors", "stargazers", "watchers", "sponsorshipsAsMaintainer", "thanks"], default:"stargazers, watchers", owner, repo}
|
context = {...context, mode: "repository", types: ["contributors", "stargazers", "watchers", "sponsorshipsAsMaintainer", "thanks"], default: "stargazers, watchers", owner, repo}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {limit, types, size, identicons, "identicons.hide":_identicons_hide, thanks, shuffle, "sponsors.custom":_sponsors} = imports.metadata.plugins.people.inputs({data, account, q}, {types:context.default})
|
let {limit, types, size, identicons, "identicons.hide": _identicons_hide, thanks, shuffle, "sponsors.custom": _sponsors} = imports.metadata.plugins.people.inputs({data, account, q}, {types: context.default})
|
||||||
//Filter types
|
//Filter types
|
||||||
types = [...new Set([...types].map(type => (context.alias[type] ?? type)).filter(type => context.types.includes(type)) ?? [])]
|
types = [...new Set([...types].map(type => (context.alias[type] ?? type)).filter(type => context.types.includes(type)) ?? [])]
|
||||||
if ((types.includes("sponsorshipsAsMaintainer")) && (_sponsors?.length)) {
|
if ((types.includes("sponsorshipsAsMaintainer")) && (_sponsors?.length)) {
|
||||||
@@ -38,13 +38,13 @@ export default async function({login, data, graphql, rest, q, queries, imports,
|
|||||||
//Rest
|
//Rest
|
||||||
if (type === "contributors") {
|
if (type === "contributors") {
|
||||||
const {owner, repo} = context
|
const {owner, repo} = context
|
||||||
const {data:nodes} = await rest.repos.listContributors({owner, repo})
|
const {data: nodes} = await rest.repos.listContributors({owner, repo})
|
||||||
result[type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl:avatar_url})))
|
result[type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl: avatar_url})))
|
||||||
}
|
}
|
||||||
else if ((type === "thanks") || (type === "sponsorshipsCustom")) {
|
else if ((type === "thanks") || (type === "sponsorshipsCustom")) {
|
||||||
const users = {thanks, sponsorshipsCustom:_sponsors}[type] ?? []
|
const users = {thanks, sponsorshipsCustom: _sponsors}[type] ?? []
|
||||||
const nodes = await Promise.all(users.map(async username => (await rest.users.getByUsername({username})).data))
|
const nodes = await Promise.all(users.map(async username => (await rest.users.getByUsername({username})).data))
|
||||||
result[{sponsorshipsCustom:"sponsorshipsAsMaintainer"}[type] ?? type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl:avatar_url})))
|
result[{sponsorshipsCustom: "sponsorshipsAsMaintainer"}[type] ?? type].push(...nodes.map(({login, avatar_url}) => ({login, avatarUrl: avatar_url})))
|
||||||
}
|
}
|
||||||
//GraphQL
|
//GraphQL
|
||||||
else {
|
else {
|
||||||
@@ -52,12 +52,12 @@ export default async function({login, data, graphql, rest, q, queries, imports,
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type} after ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > people > retrieving ${type} after ${cursor}`)
|
||||||
const {[type]:{edges}} = (
|
const {[type]: {edges}} = (
|
||||||
type in context.sponsorships
|
type in context.sponsorships
|
||||||
? (await graphql(queries.people.sponsors({login:context.owner ?? login, type, size, after:cursor ? `after: "${cursor}"` : "", target:context.sponsorships[type], account})))[account]
|
? (await graphql(queries.people.sponsors({login: context.owner ?? login, type, size, after: cursor ? `after: "${cursor}"` : "", target: context.sponsorships[type], account})))[account]
|
||||||
: context.mode === "repository"
|
: context.mode === "repository"
|
||||||
? (await graphql(queries.people.repository({login:context.owner, repository:context.repo, type, size, after:cursor ? `after: "${cursor}"` : "", account})))[account].repository
|
? (await graphql(queries.people.repository({login: context.owner, repository: context.repo, type, size, after: cursor ? `after: "${cursor}"` : "", account})))[account].repository
|
||||||
: (await graphql(queries.people({login, type, size, after:cursor ? `after: "${cursor}"` : "", account})))[account]
|
: (await graphql(queries.people({login, type, size, after: cursor ? `after: "${cursor}"` : "", account})))[account]
|
||||||
)
|
)
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
result[type].push(...edges.map(({node}) => node[context.sponsorships[type]] ?? node))
|
result[type].push(...edges.map(({node}) => node[context.sponsorships[type]] ?? node))
|
||||||
@@ -102,6 +102,6 @@ export default async function({login, data, graphql, rest, q, queries, imports,
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,21 +17,21 @@ export default async function({login, data, imports, q, queries, account}, {enab
|
|||||||
//Dev.to
|
//Dev.to
|
||||||
case "dev.to": {
|
case "dev.to": {
|
||||||
console.debug(`metrics/compute/${login}/plugins > posts > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > posts > querying api`)
|
||||||
posts = (await imports.axios.get(`https://dev.to/api/articles?username=${user}&state=fresh`)).data.map(({title, description, published_at:date, cover_image:image, url:link}) => ({title, description, date, image, link}))
|
posts = (await imports.axios.get(`https://dev.to/api/articles?username=${user}&state=fresh`)).data.map(({title, description, published_at: date, cover_image: image, url: link}) => ({title, description, date, image, link}))
|
||||||
link = `https://dev.to/${user}`
|
link = `https://dev.to/${user}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//Hashnode
|
//Hashnode
|
||||||
case "hashnode": {
|
case "hashnode": {
|
||||||
posts = (await imports.axios.post("https://api.hashnode.com", {query:queries.posts.hashnode({user})}, {headers:{"Content-type":"application/json"}})).data.data.user.publication.posts.map((
|
posts = (await imports.axios.post("https://api.hashnode.com", {query: queries.posts.hashnode({user})}, {headers: {"Content-type": "application/json"}})).data.data.user.publication.posts.map((
|
||||||
{title, brief:description, dateAdded:date, coverImage:image, slug},
|
{title, brief: description, dateAdded: date, coverImage: image, slug},
|
||||||
) => ({title, description, date, image, link:`https://hashnode.com/post/${slug}`}))
|
) => ({title, description, date, image, link: `https://hashnode.com/post/${slug}`}))
|
||||||
link = `https://hashnode.com/@${user}`
|
link = `https://hashnode.com/@${user}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
//Unsupported
|
//Unsupported
|
||||||
default:
|
default:
|
||||||
throw {error:{message:`Unsupported source "${source}"`}}
|
throw {error: {message: `Unsupported source "${source}"`}}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Format posts
|
//Format posts
|
||||||
@@ -44,19 +44,19 @@ export default async function({login, data, imports, q, queries, account}, {enab
|
|||||||
//Cover images
|
//Cover images
|
||||||
if (covers) {
|
if (covers) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > posts > formatting cover images`)
|
console.debug(`metrics/compute/${login}/plugins > posts > formatting cover images`)
|
||||||
posts = await Promise.all(posts.map(async ({image, ...post}) => ({image:await imports.imgb64(image, {width:144, height:-1}), ...post})))
|
posts = await Promise.all(posts.map(async ({image, ...post}) => ({image: await imports.imgb64(image, {width: 144, height: -1}), ...post})))
|
||||||
}
|
}
|
||||||
//Results
|
//Results
|
||||||
return {user, source, link, descriptions, covers, list:posts}
|
return {user, source, link, descriptions, covers, list: posts}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Unhandled error
|
//Unhandled error
|
||||||
throw {error:{message:"An error occured (could not retrieve posts)"}}
|
throw {error: {message: "An error occured (could not retrieve posts)"}}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default async function({login, data, imports, graphql, q, queries, accoun
|
|||||||
|
|
||||||
//Retrieve user owned projects from graphql api
|
//Retrieve user owned projects from graphql api
|
||||||
console.debug(`metrics/compute/${login}/plugins > projects > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > projects > querying api`)
|
||||||
const {[account]:{projects}} = await graphql(queries.projects.user({login, limit, account}))
|
const {[account]: {projects}} = await graphql(queries.projects.user({login, limit, account}))
|
||||||
|
|
||||||
//Retrieve repositories projects from graphql api
|
//Retrieve repositories projects from graphql api
|
||||||
for (const identifier of repositories) {
|
for (const identifier of repositories) {
|
||||||
@@ -25,7 +25,7 @@ export default async function({login, data, imports, graphql, q, queries, accoun
|
|||||||
let project = null
|
let project = null
|
||||||
for (const account of ["user", "organization"]) {
|
for (const account of ["user", "organization"]) {
|
||||||
try {
|
try {
|
||||||
({project} = (await graphql(queries.projects.repository({user, repository, id, account})))[account].repository)
|
;({project} = (await graphql(queries.projects.repository({user, repository, id, account})))[account].repository)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.debug(error)
|
console.debug(error)
|
||||||
@@ -52,9 +52,9 @@ export default async function({login, data, imports, graphql, q, queries, accoun
|
|||||||
else if (time < 30)
|
else if (time < 30)
|
||||||
updated = `${Math.floor(time)} day${time >= 2 ? "s" : ""} ago`
|
updated = `${Math.floor(time)} day${time >= 2 ? "s" : ""} ago`
|
||||||
//Format progress
|
//Format progress
|
||||||
const {enabled, todoCount:todo, inProgressCount:doing, doneCount:done} = project.progress
|
const {enabled, todoCount: todo, inProgressCount: doing, doneCount: done} = project.progress
|
||||||
//Append
|
//Append
|
||||||
list.push({name:project.name, updated, description:project.body, progress:{enabled, todo, doing, done, total:todo + doing + done}})
|
list.push({name: project.name, updated, description: project.body, progress: {enabled, todo, doing, done, total: todo + doing + done}})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Limit
|
//Limit
|
||||||
@@ -62,13 +62,13 @@ export default async function({login, data, imports, graphql, q, queries, accoun
|
|||||||
list.splice(limit)
|
list.splice(limit)
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {list, totalCount:projects.totalCount, descriptions}
|
return {list, totalCount: projects.totalCount, descriptions}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
let message = "An error occured"
|
let message = "An error occured"
|
||||||
if (error.errors?.map(({type}) => type)?.includes("INSUFFICIENT_SCOPES"))
|
if (error.errors?.map(({type}) => type)?.includes("INSUFFICIENT_SCOPES"))
|
||||||
message = "Insufficient token rights"
|
message = "Insufficient token rights"
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,23 +7,23 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {limit:_limit1, "limit.issues":_limit2, "limit.discussions":_limit3, "limit.discussions.comments":_limit4, days, details, display, ignored} = imports.metadata.plugins.reactions.inputs({data, account, q})
|
let {limit: _limit1, "limit.issues": _limit2, "limit.discussions": _limit3, "limit.discussions.comments": _limit4, days, details, display, ignored} = imports.metadata.plugins.reactions.inputs({data, account, q})
|
||||||
ignored.push(...data.shared["users.ignored"])
|
ignored.push(...data.shared["users.ignored"])
|
||||||
|
|
||||||
//Load issue comments
|
//Load issue comments
|
||||||
const comments = []
|
const comments = []
|
||||||
for (const {type, limit} of [{type:"issueComments", limit:_limit1}, {type:"issues", limit:_limit2}, {type:"repositoryDiscussionComments", limit:_limit3}, {type:"repositoryDiscussions", limit:_limit4}].filter(({limit}) => limit)) {
|
for (const {type, limit} of [{type: "issueComments", limit: _limit1}, {type: "issues", limit: _limit2}, {type: "repositoryDiscussionComments", limit: _limit3}, {type: "repositoryDiscussions", limit: _limit4}].filter(({limit}) => limit)) {
|
||||||
let cursor = null, pushed = 0
|
let cursor = null, pushed = 0
|
||||||
const fetched = []
|
const fetched = []
|
||||||
try {
|
try {
|
||||||
do {
|
do {
|
||||||
//Load issue comments
|
//Load issue comments
|
||||||
console.debug(`metrics/compute/${login}/plugins > reactions > retrieving ${type} before ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > reactions > retrieving ${type} before ${cursor}`)
|
||||||
const {user:{[type]:{edges}}} = await graphql(queries.reactions({login, type, before:cursor ? `before: "${cursor}"` : ""}))
|
const {user: {[type]: {edges}}} = await graphql(queries.reactions({login, type, before: cursor ? `before: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[0]?.cursor
|
cursor = edges?.[0]?.cursor
|
||||||
//Save issue comments
|
//Save issue comments
|
||||||
const filtered = edges
|
const filtered = edges
|
||||||
.flatMap(({node:{createdAt:created, reactions:{nodes:reactions}}}) => ({created:new Date(created), reactions:reactions.filter(({user = {}}) => !ignored.includes(user.login)).map(({content}) => content)}))
|
.flatMap(({node: {createdAt: created, reactions: {nodes: reactions}}}) => ({created: new Date(created), reactions: reactions.filter(({user = {}}) => !ignored.includes(user.login)).map(({content}) => content)}))
|
||||||
.filter(comment => Number.isFinite(days) ? comment.created < new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true)
|
.filter(comment => Number.isFinite(days) ? comment.created < new Date(Date.now() - days * 24 * 60 * 60 * 1000) : true)
|
||||||
pushed = filtered.length
|
pushed = filtered.length
|
||||||
fetched.push(...filtered)
|
fetched.push(...filtered)
|
||||||
@@ -50,13 +50,13 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
list[reaction] = (list[reaction] ?? 0) + 1
|
list[reaction] = (list[reaction] ?? 0) + 1
|
||||||
const max = Math.max(...Object.values(list))
|
const max = Math.max(...Object.values(list))
|
||||||
for (const [key, value] of Object.entries(list))
|
for (const [key, value] of Object.entries(list))
|
||||||
list[key] = {value, percentage:value / reactions.length, score:value / (display === "relative" ? max : reactions.length)}
|
list[key] = {value, percentage: value / reactions.length, score: value / (display === "relative" ? max : reactions.length)}
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {list, comments:comments.length, details, days, twemoji:q["config.twemoji"]}
|
return {list, comments: comments.length, details, days, twemoji: q["config.twemoji"]}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
let {featured} = imports.metadata.plugins.repositories.inputs({data, account, q})
|
let {featured} = imports.metadata.plugins.repositories.inputs({data, account, q})
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
const repositories = {list:[]}
|
const repositories = {list: []}
|
||||||
|
|
||||||
//Fetch repositories informations
|
//Fetch repositories informations
|
||||||
for (const repo of featured) {
|
for (const repo of featured) {
|
||||||
@@ -33,6 +33,6 @@ export default async function({login, q, imports, graphql, queries, data, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
//Load inputs
|
//Load inputs
|
||||||
let {source, limit} = imports.metadata.plugins.rss.inputs({data, account, q})
|
let {source, limit} = imports.metadata.plugins.rss.inputs({data, account, q})
|
||||||
if (!source)
|
if (!source)
|
||||||
throw {error:{message:"A RSS feed is required"}}
|
throw {error: {message: "A RSS feed is required"}}
|
||||||
|
|
||||||
//Load rss feed
|
//Load rss feed
|
||||||
const {title, description, link, items} = await (new imports.rss()).parseURL(source) //eslint-disable-line new-cap
|
const {title, description, link, items} = await (new imports.rss()).parseURL(source) //eslint-disable-line new-cap
|
||||||
const feed = items.map(({title, link, isoDate:date}) => ({title, link, date:new Date(date)}))
|
const feed = items.map(({title, link, isoDate: date}) => ({title, link, date: new Date(date)}))
|
||||||
|
|
||||||
//Limit feed
|
//Limit feed
|
||||||
if (limit > 0) {
|
if (limit > 0) {
|
||||||
@@ -22,12 +22,12 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {source:title, description, link, feed}
|
return {source: title, description, link, feed}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
|
|
||||||
//Load page
|
//Load page
|
||||||
console.debug(`metrics/compute/${login}/plugins > skyline > loading skyline.github.com/${login}/${year}`)
|
console.debug(`metrics/compute/${login}/plugins > skyline > loading skyline.github.com/${login}/${year}`)
|
||||||
await page.goto(`https://skyline.github.com/${login}/${year}`, {timeout:90 * 1000})
|
await page.goto(`https://skyline.github.com/${login}/${year}`, {timeout: 90 * 1000})
|
||||||
console.debug(`metrics/compute/${login}/plugins > skyline > waiting for initial render`)
|
console.debug(`metrics/compute/${login}/plugins > skyline > waiting for initial render`)
|
||||||
const frame = page.mainFrame()
|
const frame = page.mainFrame()
|
||||||
await page.waitForFunction('[...document.querySelectorAll("span")].map(span => span.innerText).includes("Share on Twitter")', {timeout:90 * 1000})
|
await page.waitForFunction('[...document.querySelectorAll("span")].map(span => span.innerText).includes("Share on Twitter")', {timeout: 90 * 1000})
|
||||||
await frame.evaluate(() => [...document.querySelectorAll("button, footer, a")].map(element => element.remove()))
|
await frame.evaluate(() => [...document.querySelectorAll("button, footer, a")].map(element => element.remove()))
|
||||||
|
|
||||||
//Generate gif
|
//Generate gif
|
||||||
console.debug(`metrics/compute/${login}/plugins > skyline > generating frames`)
|
console.debug(`metrics/compute/${login}/plugins > skyline > generating frames`)
|
||||||
const animation = compatibility ? await imports.record({page, width, height, frames, scale:quality}) : await imports.gif({page, width, height, frames, quality:Math.max(1, quality * 20)})
|
const animation = compatibility ? await imports.record({page, width, height, frames, scale: quality}) : await imports.gif({page, width, height, frames, quality: Math.max(1, quality * 20)})
|
||||||
|
|
||||||
//Close puppeteer
|
//Close puppeteer
|
||||||
await browser.close()
|
await browser.close()
|
||||||
@@ -42,6 +42,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
|
|
||||||
//Query description and goal
|
//Query description and goal
|
||||||
console.debug(`metrics/compute/${login}/plugins > sponsors > querying sponsors and goal`)
|
console.debug(`metrics/compute/${login}/plugins > sponsors > querying sponsors and goal`)
|
||||||
const {[account]:{sponsorsListing:{fullDescription, activeGoal}}} = await graphql(queries.sponsors.description({login, account}))
|
const {[account]: {sponsorsListing: {fullDescription, activeGoal}}} = await graphql(queries.sponsors.description({login, account}))
|
||||||
const about = await imports.markdown(fullDescription, {mode:"multiline"})
|
const about = await imports.markdown(fullDescription, {mode: "multiline"})
|
||||||
const goal = activeGoal ? {progress:activeGoal.percentComplete, title:activeGoal.title, description:await imports.markdown(activeGoal.description)} : null
|
const goal = activeGoal ? {progress: activeGoal.percentComplete, title: activeGoal.title, description: await imports.markdown(activeGoal.description)} : null
|
||||||
const count = {total:{count:0, user:0, organization:0}, active:{total:0, user:0, organization:0}, past:{total:0, user:0, organization:0}}
|
const count = {total: {count: 0, user: 0, organization: 0}, active: {total: 0, user: 0, organization: 0}, past: {total: 0, user: 0, organization: 0}}
|
||||||
|
|
||||||
//Query active sponsors
|
//Query active sponsors
|
||||||
let list = []
|
let list = []
|
||||||
@@ -24,13 +24,13 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/sponsors > retrieving sponsors after ${cursor}`)
|
console.debug(`metrics/compute/${login}/sponsors > retrieving sponsors after ${cursor}`)
|
||||||
const {[account]:{sponsorshipsAsMaintainer:{edges, nodes}}} = await graphql(queries.sponsors.active({login, account, after:cursor ? `after: "${cursor}"` : "", size:Math.round(size*1.5)}))
|
const {[account]: {sponsorshipsAsMaintainer: {edges, nodes}}} = await graphql(queries.sponsors.active({login, account, after: cursor ? `after: "${cursor}"` : "", size: Math.round(size * 1.5)}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
fetched.push(...nodes)
|
fetched.push(...nodes)
|
||||||
pushed = nodes.length
|
pushed = nodes.length
|
||||||
console.debug(`metrics/compute/${login}/sponsors > retrieved ${pushed} sponsors after ${cursor}`)
|
console.debug(`metrics/compute/${login}/sponsors > retrieved ${pushed} sponsors after ${cursor}`)
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
list.push(...fetched.map(({sponsorEntity:{login, avatarUrl, url:organization = null}, tier}) => ({login, avatarUrl, type:organization ? "organization" : "user", amount:tier?.monthlyPriceInDollars ?? null, past:false})))
|
list.push(...fetched.map(({sponsorEntity: {login, avatarUrl, url: organization = null}, tier}) => ({login, avatarUrl, type: organization ? "organization" : "user", amount: tier?.monthlyPriceInDollars ?? null, past: false})))
|
||||||
await Promise.all(list.map(async user => user.avatar = await imports.imgb64(user.avatarUrl)))
|
await Promise.all(list.map(async user => user.avatar = await imports.imgb64(user.avatarUrl)))
|
||||||
count.active.total = list.length
|
count.active.total = list.length
|
||||||
count.active.user = list.filter(user => user.type === "user").length
|
count.active.user = list.filter(user => user.type === "user").length
|
||||||
@@ -48,18 +48,18 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/sponsors > retrieving sponsors events after ${cursor}`)
|
console.debug(`metrics/compute/${login}/sponsors > retrieving sponsors events after ${cursor}`)
|
||||||
const {[account]:{sponsorsActivities:{edges, nodes}}} = await graphql(queries.sponsors.all({login, account, after:cursor ? `after: "${cursor}"` : "", size:Math.round(size*1.5)}))
|
const {[account]: {sponsorsActivities: {edges, nodes}}} = await graphql(queries.sponsors.all({login, account, after: cursor ? `after: "${cursor}"` : "", size: Math.round(size * 1.5)}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
fetched.push(...nodes)
|
fetched.push(...nodes)
|
||||||
pushed = nodes.length
|
pushed = nodes.length
|
||||||
console.debug(`metrics/compute/${login}/sponsors > retrieved ${pushed} sponsors events after ${cursor}`)
|
console.debug(`metrics/compute/${login}/sponsors > retrieved ${pushed} sponsors events after ${cursor}`)
|
||||||
} while ((pushed) && (cursor))
|
} while ((pushed) && (cursor))
|
||||||
users.push(...fetched.map(({sponsor:{login, avatarUrl, url:organization = null}, sponsorsTier}) => ({login, avatarUrl, type:organization ? "organization" : "user", amount:sponsorsTier?.monthlyPriceInDollars ?? null, past:true})))
|
users.push(...fetched.map(({sponsor: {login, avatarUrl, url: organization = null}, sponsorsTier}) => ({login, avatarUrl, type: organization ? "organization" : "user", amount: sponsorsTier?.monthlyPriceInDollars ?? null, past: true})))
|
||||||
}
|
}
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
if (!active.has(user.login)) {
|
if (!active.has(user.login)) {
|
||||||
active.add(user.login)
|
active.add(user.login)
|
||||||
list.push({...user, avatar:await imports.imgb64(user.avatarUrl)})
|
list.push({...user, avatar: await imports.imgb64(user.avatarUrl)})
|
||||||
count.past.total++
|
count.past.total++
|
||||||
count.past[user.type]++
|
count.past[user.type]++
|
||||||
}
|
}
|
||||||
@@ -77,6 +77,6 @@ export default async function({login, q, imports, data, graphql, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,54 +7,54 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {sections, user, limit, lines, "lines.snippet":codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
let {sections, user, limit, lines, "lines.snippet": codelines} = imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
||||||
if (!user)
|
if (!user)
|
||||||
throw {error:{message:"You must provide a stackoverflow user id"}}
|
throw {error: {message: "You must provide a stackoverflow user id"}}
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
//See https://api.stackexchange.com/docs
|
//See https://api.stackexchange.com/docs
|
||||||
const api = {base:"https://api.stackexchange.com/2.2", user:`https://api.stackexchange.com/2.2/users/${user}`}
|
const api = {base: "https://api.stackexchange.com/2.2", user: `https://api.stackexchange.com/2.2/users/${user}`}
|
||||||
const filters = {user:"!0Z-LvgkLYnTCu1858)*D0lcx2", answer:"!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question:"!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"}
|
const filters = {user: "!0Z-LvgkLYnTCu1858)*D0lcx2", answer: "!7goY5TLWwCz.BaGpe)tv5C6Bks2q8siMH6", question: "!)EhwvzgX*hrClxjLzqxiZHHbTPRE5Pb3B9vvRaqCx5-ZY.vPr"}
|
||||||
const result = {sections, lines}
|
const result = {sections, lines}
|
||||||
|
|
||||||
//Stackoverflow user metrics
|
//Stackoverflow user metrics
|
||||||
{
|
{
|
||||||
//Account metrics
|
//Account metrics
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for user ${user}`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for user ${user}`)
|
||||||
const {data:{items:[{reputation, badge_counts:{bronze, silver, gold}, answer_count:answers, question_count:questions, view_count:views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`)
|
const {data: {items: [{reputation, badge_counts: {bronze, silver, gold}, answer_count: answers, question_count: questions, view_count: views}]}} = await imports.axios.get(`${api.user}?site=stackoverflow&filter=${filters.user}`)
|
||||||
const {data:{total:comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`)
|
const {data: {total: comments}} = await imports.axios.get(`${api.user}/comments?site=stackoverflow&filter=total`)
|
||||||
//Save result
|
//Save result
|
||||||
result.user = {id:user, reputation, badges:bronze + silver + gold, questions, answers, comments, views}
|
result.user = {id: user, reputation, badges: bronze + silver + gold, questions, answers, comments, views}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Answers
|
//Answers
|
||||||
for (const {key, sort} of [{key:"answers-recent", sort:"sort=activity&order=desc"}, {key:"answers-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
|
for (const {key, sort} of [{key: "answers-recent", sort: "sort=activity&order=desc"}, {key: "answers-top", sort: "sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
|
||||||
//Load and format answers
|
//Load and format answers
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
||||||
const {data:{items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`)
|
const {data: {items}} = await imports.axios.get(`${api.user}/answers?site=stackoverflow&pagesize=${limit}&filter=${filters.answer}&${sort}`)
|
||||||
result[key] = await Promise.all(items.map(item => format.answer(item, {imports, codelines})))
|
result[key] = await Promise.all(items.map(item => format.answer(item, {imports, codelines})))
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
||||||
//Load related questions
|
//Load related questions
|
||||||
const ids = result[key].map(({question_id}) => question_id).filter(id => id)
|
const ids = result[key].map(({question_id}) => question_id).filter(id => id)
|
||||||
if (ids) {
|
if (ids) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
||||||
const {data:{items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`)
|
const {data: {items}} = await imports.axios.get(`${api.base}/questions/${ids.join(";")}?site=stackoverflow&filter=${filters.question}`)
|
||||||
await Promise.all(items.map(item => format.question(item, {imports, codelines})))
|
await Promise.all(items.map(item => format.question(item, {imports, codelines})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Questions
|
//Questions
|
||||||
for (const {key, sort} of [{key:"questions-recent", sort:"sort=activity&order=desc"}, {key:"questions-top", sort:"sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
|
for (const {key, sort} of [{key: "questions-recent", sort: "sort=activity&order=desc"}, {key: "questions-top", sort: "sort=votes&order=desc"}].filter(({key}) => sections.includes(key))) {
|
||||||
//Load and format questions
|
//Load and format questions
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > querying api for ${key}`)
|
||||||
const {data:{items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`)
|
const {data: {items}} = await imports.axios.get(`${api.user}/questions?site=stackoverflow&pagesize=${limit}&filter=${filters.question}&${sort}`)
|
||||||
result[key] = await Promise.all(items.map(item => format.question(item, {imports, codelines})))
|
result[key] = await Promise.all(items.map(item => format.question(item, {imports, codelines})))
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loaded ${result[key].length} items`)
|
||||||
//Load related answers
|
//Load related answers
|
||||||
const ids = result[key].map(({accepted_answer_id}) => accepted_answer_id).filter(id => id)
|
const ids = result[key].map(({accepted_answer_id}) => accepted_answer_id).filter(id => id)
|
||||||
if (ids) {
|
if (ids) {
|
||||||
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
console.debug(`metrics/compute/${login}/plugins > stackoverflow > loading ${ids.length} related items`)
|
||||||
const {data:{items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`)
|
const {data: {items}} = await imports.axios.get(`${api.base}/answers/${ids.join(";")}?site=stackoverflow&filter=${filters.answer}`)
|
||||||
await Promise.all(items.map(item => format.answer(item, {imports, codelines})))
|
await Promise.all(items.map(item => format.answer(item, {imports, codelines})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,30 +66,30 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Formatters
|
//Formatters
|
||||||
const format = {
|
const format = {
|
||||||
/**Cached */
|
/**Cached */
|
||||||
cached:new Map(),
|
cached: new Map(),
|
||||||
/**Format stackoverflow code snippets */
|
/**Format stackoverflow code snippets */
|
||||||
code(text) {
|
code(text) {
|
||||||
return text.replace(/<!-- language: lang-(?<lang>\w+) -->\s*(?<snippet> {4}[\s\S]+?)(?=(?:<!-- end snippet -->)|(?:<!-- language: lang-))/g, "```$<lang>\n$<snippet>```")
|
return text.replace(/<!-- language: lang-(?<lang>\w+) -->\s*(?<snippet> {4}[\s\S]+?)(?=(?:<!-- end snippet -->)|(?:<!-- language: lang-))/g, "```$<lang>\n$<snippet>```")
|
||||||
},
|
},
|
||||||
/**Format answers */
|
/**Format answers */
|
||||||
async answer({body_markdown:body, score, up_vote_count:upvotes, down_vote_count:downvotes, is_accepted:accepted, comment_count:comments = 0, creation_date, owner:{display_name:author}, link, answer_id:id, question_id}, {imports, codelines}) {
|
async answer({body_markdown: body, score, up_vote_count: upvotes, down_vote_count: downvotes, is_accepted: accepted, comment_count: comments = 0, creation_date, owner: {display_name: author}, link, answer_id: id, question_id}, {imports, codelines}) {
|
||||||
const formatted = {
|
const formatted = {
|
||||||
type:"answer",
|
type: "answer",
|
||||||
body:await imports.markdown(format.code(imports.htmlunescape(body)), {codelines}),
|
body: await imports.markdown(format.code(imports.htmlunescape(body)), {codelines}),
|
||||||
score,
|
score,
|
||||||
upvotes,
|
upvotes,
|
||||||
downvotes,
|
downvotes,
|
||||||
accepted,
|
accepted,
|
||||||
comments,
|
comments,
|
||||||
author,
|
author,
|
||||||
created:imports.format.date(creation_date * 1000, {date:true}),
|
created: imports.format.date(creation_date * 1000, {date: true}),
|
||||||
link,
|
link,
|
||||||
id,
|
id,
|
||||||
question_id,
|
question_id,
|
||||||
@@ -104,28 +104,28 @@ const format = {
|
|||||||
async question(
|
async question(
|
||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
body_markdown:body,
|
body_markdown: body,
|
||||||
score,
|
score,
|
||||||
up_vote_count:upvotes,
|
up_vote_count: upvotes,
|
||||||
down_vote_count:downvotes,
|
down_vote_count: downvotes,
|
||||||
favorite_count:favorites,
|
favorite_count: favorites,
|
||||||
tags,
|
tags,
|
||||||
is_answered:answered,
|
is_answered: answered,
|
||||||
answer_count:answers,
|
answer_count: answers,
|
||||||
comment_count:comments = 0,
|
comment_count: comments = 0,
|
||||||
view_count:views,
|
view_count: views,
|
||||||
creation_date,
|
creation_date,
|
||||||
owner:{display_name:author},
|
owner: {display_name: author},
|
||||||
link,
|
link,
|
||||||
question_id:id,
|
question_id: id,
|
||||||
accepted_answer_id = null,
|
accepted_answer_id = null,
|
||||||
},
|
},
|
||||||
{imports, codelines},
|
{imports, codelines},
|
||||||
) {
|
) {
|
||||||
const formatted = {
|
const formatted = {
|
||||||
type:"question",
|
type: "question",
|
||||||
title:await imports.markdown(title),
|
title: await imports.markdown(title),
|
||||||
body:await imports.markdown(format.code(imports.htmlunescape(body)), {codelines}),
|
body: await imports.markdown(format.code(imports.htmlunescape(body)), {codelines}),
|
||||||
score,
|
score,
|
||||||
upvotes,
|
upvotes,
|
||||||
downvotes,
|
downvotes,
|
||||||
@@ -136,7 +136,7 @@ const format = {
|
|||||||
comments,
|
comments,
|
||||||
views,
|
views,
|
||||||
author,
|
author,
|
||||||
created:imports.format.date(creation_date * 1000, {date:true}),
|
created: imports.format.date(creation_date * 1000, {date: true}),
|
||||||
link,
|
link,
|
||||||
id,
|
id,
|
||||||
accepted_answer_id,
|
accepted_answer_id,
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {"charts.type":_charts} = imports.metadata.plugins.stargazers.inputs({data, account, q})
|
let {"charts.type": _charts} = imports.metadata.plugins.stargazers.inputs({data, account, q})
|
||||||
|
|
||||||
//Retrieve stargazers from graphql api
|
//Retrieve stargazers from graphql api
|
||||||
console.debug(`metrics/compute/${login}/plugins > stargazers > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > stargazers > querying api`)
|
||||||
const repositories = data.user.repositories.nodes.map(({name:repository, owner:{login:owner}}) => ({repository, owner})) ?? []
|
const repositories = data.user.repositories.nodes.map(({name: repository, owner: {login: owner}}) => ({repository, owner})) ?? []
|
||||||
const dates = []
|
const dates = []
|
||||||
for (const {repository, owner} of repositories) {
|
for (const {repository, owner} of repositories) {
|
||||||
//Iterate through stargazers
|
//Iterate through stargazers
|
||||||
@@ -20,7 +20,7 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
let pushed = 0
|
let pushed = 0
|
||||||
do {
|
do {
|
||||||
console.debug(`metrics/compute/${login}/plugins > stargazers > retrieving stargazers of ${repository} after ${cursor}`)
|
console.debug(`metrics/compute/${login}/plugins > stargazers > retrieving stargazers of ${repository} after ${cursor}`)
|
||||||
const {repository:{stargazers:{edges}}} = await graphql(queries.stargazers({login:owner, repository, after:cursor ? `after: "${cursor}"` : ""}))
|
const {repository: {stargazers: {edges}}} = await graphql(queries.stargazers({login: owner, repository, after: cursor ? `after: "${cursor}"` : ""}))
|
||||||
cursor = edges?.[edges?.length - 1]?.cursor
|
cursor = edges?.[edges?.length - 1]?.cursor
|
||||||
dates.push(...edges.map(({starredAt}) => new Date(starredAt)))
|
dates.push(...edges.map(({starredAt}) => new Date(starredAt)))
|
||||||
pushed = edges.length
|
pushed = edges.length
|
||||||
@@ -32,7 +32,7 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
|
|
||||||
//Compute stargazers increments
|
//Compute stargazers increments
|
||||||
const days = 14 * (1 + data.large / 2)
|
const days = 14 * (1 + data.large / 2)
|
||||||
const increments = {dates:Object.fromEntries([...new Array(days).fill(null).map((_, i) => [new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().slice(0, 10), 0]).reverse()]), max:NaN, min:NaN}
|
const increments = {dates: Object.fromEntries([...new Array(days).fill(null).map((_, i) => [new Date(Date.now() - i * 24 * 60 * 60 * 1000).toISOString().slice(0, 10), 0]).reverse()]), max: NaN, min: NaN}
|
||||||
dates
|
dates
|
||||||
.map(date => date.toISOString().slice(0, 10))
|
.map(date => date.toISOString().slice(0, 10))
|
||||||
.filter(date => date in increments.dates)
|
.filter(date => date in increments.dates)
|
||||||
@@ -42,12 +42,12 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
|
|
||||||
//Compute total stargazers
|
//Compute total stargazers
|
||||||
let {stargazers} = data.computed.repositories
|
let {stargazers} = data.computed.repositories
|
||||||
const total = {dates:{...increments.dates}, max:NaN, min:NaN}
|
const total = {dates: {...increments.dates}, max: NaN, min: NaN}
|
||||||
{
|
{
|
||||||
const dates = Object.keys(total.dates)
|
const dates = Object.keys(total.dates)
|
||||||
for (let i = dates.length - 1; i >= 0; i--) {
|
for (let i = dates.length - 1; i >= 0; i--) {
|
||||||
const date = dates[i], tomorrow = dates[i + 1]
|
const date = dates[i], tomorrow = dates[i + 1]
|
||||||
stargazers -= (increments.dates[tomorrow] ?? 0)
|
stargazers -= increments.dates[tomorrow] ?? 0
|
||||||
total.dates[date] = stargazers
|
total.dates[date] = stargazers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,22 +61,23 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
let charts = null
|
let charts = null
|
||||||
if (_charts === "chartist") {
|
if (_charts === "chartist") {
|
||||||
console.debug(`metrics/compute/${login}/plugins > stargazers > generating charts`)
|
console.debug(`metrics/compute/${login}/plugins > stargazers > generating charts`)
|
||||||
charts = await Promise.all([{data:total, low:total.min, high:total.max}, {data:increments, ref:0, low:increments.min, high:increments.max, sign:true}].map(({data:{dates:set}, high, low, ref, sign = false}) => imports.chartist("line", {
|
charts = await Promise.all([{data: total, low: total.min, high: total.max}, {data: increments, ref: 0, low: increments.min, high: increments.max, sign: true}].map(({data: {dates: set}, high, low, ref, sign = false}) =>
|
||||||
width:480 * (1 + data.large),
|
imports.chartist("line", {
|
||||||
height:160,
|
width: 480 * (1 + data.large),
|
||||||
showPoint:true,
|
height: 160,
|
||||||
axisX:{showGrid:false},
|
showPoint: true,
|
||||||
axisY:{showLabel:false, offset:20, labelInterpolationFnc:value => imports.format(value, {sign}), high, low, referenceValue:ref},
|
axisX: {showGrid: false},
|
||||||
showArea:true,
|
axisY: {showLabel: false, offset: 20, labelInterpolationFnc: value => imports.format(value, {sign}), high, low, referenceValue: ref},
|
||||||
fullWidth:true,
|
showArea: true,
|
||||||
|
fullWidth: true,
|
||||||
}, {
|
}, {
|
||||||
labels:Object.keys(set).map((date, i, a) => {
|
labels: Object.keys(set).map((date, i, a) => {
|
||||||
const day = date.substring(date.length - 2)
|
const day = date.substring(date.length - 2)
|
||||||
if ((i === 0) || ((a[i - 1]) && (date.substring(0, 7) !== a[i - 1].substring(0, 7))))
|
if ((i === 0) || ((a[i - 1]) && (date.substring(0, 7) !== a[i - 1].substring(0, 7))))
|
||||||
return `${day} ${months[Number(date.substring(5, 7))]}`
|
return `${day} ${months[Number(date.substring(5, 7))]}`
|
||||||
return day
|
return day
|
||||||
}),
|
}),
|
||||||
series:[Object.values(set)],
|
series: [Object.values(set)],
|
||||||
})
|
})
|
||||||
))
|
))
|
||||||
data.postscripts.push(`(${function(format) {
|
data.postscripts.push(`(${function(format) {
|
||||||
@@ -99,6 +100,6 @@ export default async function({login, graphql, data, imports, q, queries, accoun
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {limit, ignored, only, "limit.repositories":_limit, languages, "limit.languages":_limit_languages, "shuffle.repositories":_shuffle} = imports.metadata.plugins.starlists.inputs({data, account, q})
|
let {limit, ignored, only, "limit.repositories": _limit, languages, "limit.languages": _limit_languages, "shuffle.repositories": _shuffle} = imports.metadata.plugins.starlists.inputs({data, account, q})
|
||||||
ignored = ignored.map(imports.stripemojis)
|
ignored = ignored.map(imports.stripemojis)
|
||||||
only = only.map(imports.stripemojis)
|
only = only.map(imports.stripemojis)
|
||||||
|
|
||||||
@@ -20,12 +20,13 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
//Fetch star lists
|
//Fetch star lists
|
||||||
console.debug(`metrics/compute/${login}/plugins > starlists > fetching lists`)
|
console.debug(`metrics/compute/${login}/plugins > starlists > fetching lists`)
|
||||||
await page.goto(`https://github.com/${login}?tab=stars`)
|
await page.goto(`https://github.com/${login}?tab=stars`)
|
||||||
let lists = (await page.evaluate(login => [...document.querySelectorAll(`[href^='/stars/${login}/lists']`)].map(element => ({
|
let lists = (await page.evaluate(login =>
|
||||||
link:element.href,
|
[...document.querySelectorAll(`[href^='/stars/${login}/lists']`)].map(element => ({
|
||||||
name:element.querySelector("h3")?.innerText ?? "",
|
link: element.href,
|
||||||
description:element.querySelector("span")?.innerText ?? "",
|
name: element.querySelector("h3")?.innerText ?? "",
|
||||||
count:Number(element.querySelector("div")?.innerText.match(/(?<count>\d+)/)?.groups.count),
|
description: element.querySelector("span")?.innerText ?? "",
|
||||||
repositories:[],
|
count: Number(element.querySelector("div")?.innerText.match(/(?<count>\d+)/)?.groups.count),
|
||||||
|
repositories: [],
|
||||||
})), login))
|
})), login))
|
||||||
const count = lists.length
|
const count = lists.length
|
||||||
console.debug(`metrics/compute/${login}/plugins > starlists > found [${lists.map(({name}) => name)}]`)
|
console.debug(`metrics/compute/${login}/plugins > starlists > found [${lists.map(({name}) => name)}]`)
|
||||||
@@ -49,15 +50,16 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
console.debug(`metrics/compute/${login}/plugins > starlists > fetching page ${i}`)
|
console.debug(`metrics/compute/${login}/plugins > starlists > fetching page ${i}`)
|
||||||
await page.goto(`${list.link}?page=${i}`)
|
await page.goto(`${list.link}?page=${i}`)
|
||||||
repositories.push(
|
repositories.push(
|
||||||
...await page.evaluate(() => [...document.querySelectorAll("#user-list-repositories > div:not(.paginate-container)")].map(element => ({
|
...await page.evaluate(() =>
|
||||||
name:element.querySelector("div:first-child")?.innerText.replace(" / ", "/") ?? "",
|
[...document.querySelectorAll("#user-list-repositories > div:not(.paginate-container)")].map(element => ({
|
||||||
description:element.querySelector(".py-1")?.innerText ?? "",
|
name: element.querySelector("div:first-child")?.innerText.replace(" / ", "/") ?? "",
|
||||||
language:{
|
description: element.querySelector(".py-1")?.innerText ?? "",
|
||||||
name:element.querySelector("[itemprop='programmingLanguage']")?.innerText ?? "",
|
language: {
|
||||||
color:element.querySelector(".repo-language-color")?.style?.backgroundColor?.match(/\d+/g)?.map(x => Number(x).toString(16).padStart(2, "0")).join("") ?? null,
|
name: element.querySelector("[itemprop='programmingLanguage']")?.innerText ?? "",
|
||||||
|
color: element.querySelector(".repo-language-color")?.style?.backgroundColor?.match(/\d+/g)?.map(x => Number(x).toString(16).padStart(2, "0")).join("") ?? null,
|
||||||
},
|
},
|
||||||
stargazers:Number(element.querySelector("[href$='/stargazers']")?.innerText.trim().replace(/[^\d]/g, "") ?? NaN),
|
stargazers: Number(element.querySelector("[href$='/stargazers']")?.innerText.trim().replace(/[^\d]/g, "") ?? NaN),
|
||||||
forks:Number(element.querySelector("[href$='/network/members']")?.innerText.trim().replace(/[^\d]/g, "") ?? NaN),
|
forks: Number(element.querySelector("[href$='/network/members']")?.innerText.trim().replace(/[^\d]/g, "") ?? NaN),
|
||||||
}))
|
}))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -73,7 +75,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
//Compute languages statistics
|
//Compute languages statistics
|
||||||
if (languages) {
|
if (languages) {
|
||||||
list.languages = {}
|
list.languages = {}
|
||||||
for (const {language:{name, color}} of repositories) {
|
for (const {language: {name, color}} of repositories) {
|
||||||
if (name)
|
if (name)
|
||||||
list.languages[name] = (list.languages[name] ?? 0) + 1
|
list.languages[name] = (list.languages[name] ?? 0) + 1
|
||||||
if (color)
|
if (color)
|
||||||
@@ -81,7 +83,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
list.languages = Object.entries(list.languages).sort((a, b) => b[1] - a[1]).slice(0, _limit_languages || Infinity)
|
list.languages = Object.entries(list.languages).sort((a, b) => b[1] - a[1]).slice(0, _limit_languages || Infinity)
|
||||||
const visible = list.languages.map(([_, value]) => value).reduce((a, b) => a + b, 0)
|
const visible = list.languages.map(([_, value]) => value).reduce((a, b) => a + b, 0)
|
||||||
list.languages = list.languages.map(([name, value]) => ({name, value, color:name in colors ? `#${colors[name]}` : null, x:0, p:value / visible}))
|
list.languages = list.languages.map(([name, value]) => ({name, value, color: name in colors ? `#${colors[name]}` : null, x: 0, p: value / visible}))
|
||||||
for (let i = 1; i < list.languages.length; i++)
|
for (let i = 1; i < list.languages.length; i++)
|
||||||
list.languages[i].x = (list.languages[i - 1]?.x ?? 0) + (list.languages[i - 1]?.value ?? 0) / visible
|
list.languages[i].x = (list.languages[i - 1]?.x ?? 0) + (list.languages[i - 1]?.value ?? 0) / visible
|
||||||
}
|
}
|
||||||
@@ -99,6 +101,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export default async function({login, data, graphql, q, queries, imports, accoun
|
|||||||
|
|
||||||
//Retrieve user stars from graphql api
|
//Retrieve user stars from graphql api
|
||||||
console.debug(`metrics/compute/${login}/plugins > stars > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > stars > querying api`)
|
||||||
const {user:{starredRepositories:{edges:repositories}}} = await graphql(queries.stars({login, limit}))
|
const {user: {starredRepositories: {edges: repositories}}} = await graphql(queries.stars({login, limit}))
|
||||||
|
|
||||||
//Format starred repositories
|
//Format starred repositories
|
||||||
for (const edge of repositories) {
|
for (const edge of repositories) {
|
||||||
@@ -32,6 +32,6 @@ export default async function({login, data, graphql, q, queries, imports, accoun
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
imports.metadata.plugins.stackoverflow.inputs({data, account, q})
|
||||||
|
|
||||||
//Start puppeteer and navigate to github.community
|
//Start puppeteer and navigate to github.community
|
||||||
const result = {stats:{solutions:0, posts:0, topics:0, received:0}, badges:{count:0}}
|
const result = {stats: {solutions: 0, posts: 0, topics: 0, received: 0}, badges: {count: 0}}
|
||||||
console.debug(`metrics/compute/${login}/plugins > support > starting browser`)
|
console.debug(`metrics/compute/${login}/plugins > support > starting browser`)
|
||||||
const browser = await imports.puppeteer.launch()
|
const browser = await imports.puppeteer.launch()
|
||||||
console.debug(`metrics/compute/${login}/plugins > support > started ${await browser.version()}`)
|
console.debug(`metrics/compute/${login}/plugins > support > started ${await browser.version()}`)
|
||||||
@@ -21,10 +21,10 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
await page.goto(`https://github.community/u/${login}`)
|
await page.goto(`https://github.community/u/${login}`)
|
||||||
const frame = page.mainFrame()
|
const frame = page.mainFrame()
|
||||||
try {
|
try {
|
||||||
await frame.waitForSelector(".user-profile-names", {timeout:5000})
|
await frame.waitForSelector(".user-profile-names", {timeout: 5000})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
throw {error:{message:"Could not find matching account on github.community"}}
|
throw {error: {message: "Could not find matching account on github.community"}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,8 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
Object.assign(
|
Object.assign(
|
||||||
result.stats,
|
result.stats,
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
(await frame.evaluate(() => [...document.querySelectorAll(".stats-section li")].map(el => [
|
(await frame.evaluate(() =>
|
||||||
|
[...document.querySelectorAll(".stats-section li")].map(el => [
|
||||||
el.querySelector(".label").innerText.trim().toLocaleLowerCase(),
|
el.querySelector(".label").innerText.trim().toLocaleLowerCase(),
|
||||||
el.querySelector(".value").innerText.trim().toLocaleLowerCase(),
|
el.querySelector(".value").innerText.trim().toLocaleLowerCase(),
|
||||||
])
|
])
|
||||||
@@ -64,8 +65,8 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
const frame = page.mainFrame()
|
const frame = page.mainFrame()
|
||||||
await frame.waitForSelector(".badge-group-list")
|
await frame.waitForSelector(".badge-group-list")
|
||||||
const badges = await frame.evaluate(() => ({
|
const badges = await frame.evaluate(() => ({
|
||||||
uniques:[...document.querySelectorAll(".badge-card .badge-link")].map(el => el.innerText),
|
uniques: [...document.querySelectorAll(".badge-card .badge-link")].map(el => el.innerText),
|
||||||
multiples:[...document.querySelectorAll(".grant-count")].map(el => Number(el.innerText)),
|
multiples: [...document.querySelectorAll(".grant-count")].map(el => Number(el.innerText)),
|
||||||
}))
|
}))
|
||||||
badges.count = badges.uniques.length + (badges.multiples.reduce((a, b) => a + b, 0) - badges.multiples.length)
|
badges.count = badges.uniques.length + (badges.multiples.reduce((a, b) => a + b, 0) - badges.multiples.length)
|
||||||
result.badges = badges
|
result.badges = badges
|
||||||
@@ -82,6 +83,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default async function({login, data, imports, q, account}, {enabled = fal
|
|||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {sort, mode, limit} = imports.metadata.plugins.topics.inputs({data, account, q})
|
let {sort, mode, limit} = imports.metadata.plugins.topics.inputs({data, account, q})
|
||||||
const type = {starred:"labels", labels:"labels", mastered:"icons", icons:"icons"}[mode]
|
const type = {starred: "labels", labels: "labels", mastered: "icons", icons: "icons"}[mode]
|
||||||
const shuffle = (sort === "random")
|
const shuffle = (sort === "random")
|
||||||
|
|
||||||
//Start puppeteer and navigate to topics
|
//Start puppeteer and navigate to topics
|
||||||
@@ -27,10 +27,11 @@ export default async function({login, data, imports, q, account}, {enabled = fal
|
|||||||
const frame = page.mainFrame()
|
const frame = page.mainFrame()
|
||||||
//Extract topics
|
//Extract topics
|
||||||
await Promise.race([frame.waitForSelector("ul.repo-list"), frame.waitForSelector(".blankslate")])
|
await Promise.race([frame.waitForSelector("ul.repo-list"), frame.waitForSelector(".blankslate")])
|
||||||
const starred = await frame.evaluate(() => [...document.querySelectorAll("ul.repo-list li")].map(li => ({
|
const starred = await frame.evaluate(() =>
|
||||||
name:li.querySelector(".f3").innerText,
|
[...document.querySelectorAll("ul.repo-list li")].map(li => ({
|
||||||
description:li.querySelector(".f5").innerText,
|
name: li.querySelector(".f3").innerText,
|
||||||
icon:li.querySelector("img")?.src ?? null,
|
description: li.querySelector(".f5").innerText,
|
||||||
|
icon: li.querySelector("img")?.src ?? null,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
console.debug(`metrics/compute/${login}/plugins > topics > extracted ${starred.length} starred topics`)
|
console.debug(`metrics/compute/${login}/plugins > topics > extracted ${starred.length} starred topics`)
|
||||||
@@ -57,7 +58,7 @@ export default async function({login, data, imports, q, account}, {enabled = fal
|
|||||||
console.debug(`metrics/compute/${login}/plugins > topics > keeping only ${limit} topics`)
|
console.debug(`metrics/compute/${login}/plugins > topics > keeping only ${limit} topics`)
|
||||||
const removed = topics.splice(limit)
|
const removed = topics.splice(limit)
|
||||||
if (removed.length)
|
if (removed.length)
|
||||||
topics.push({name:`And ${removed.length} more...`, description:removed.map(({name}) => name).join(", "), icon:null})
|
topics.push({name: `And ${removed.length} more...`, description: removed.map(({name}) => name).join(", "), icon: null})
|
||||||
}
|
}
|
||||||
|
|
||||||
//Convert icons to base64
|
//Convert icons to base64
|
||||||
@@ -67,7 +68,7 @@ export default async function({login, data, imports, q, account}, {enabled = fal
|
|||||||
console.debug(`metrics/compute/${login}/plugins > topics > processing ${topic.name}`)
|
console.debug(`metrics/compute/${login}/plugins > topics > processing ${topic.name}`)
|
||||||
const {icon} = topic
|
const {icon} = topic
|
||||||
topic.icon = await imports.imgb64(icon)
|
topic.icon = await imports.imgb64(icon)
|
||||||
topic.icon24 = await imports.imgb64(icon, {force:true, width:24, height:24})
|
topic.icon24 = await imports.imgb64(icon, {force: true, width: 24, height: 24})
|
||||||
}
|
}
|
||||||
//Escape HTML description
|
//Escape HTML description
|
||||||
topic.description = imports.htmlescape(topic.description)
|
topic.description = imports.htmlescape(topic.description)
|
||||||
@@ -86,12 +87,12 @@ export default async function({login, data, imports, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {mode, type, list:topics}
|
return {mode, type, list: topics}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.error?.message)
|
if (error.error?.message)
|
||||||
throw error
|
throw error
|
||||||
throw {error:{message:"An error occured", instance:error}}
|
throw {error: {message: "An error occured", instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ export default async function({login, imports, data, rest, q, account}, {enabled
|
|||||||
skipped.push(...data.shared["repositories.skipped"])
|
skipped.push(...data.shared["repositories.skipped"])
|
||||||
|
|
||||||
//Repositories
|
//Repositories
|
||||||
const repositories = data.user.repositories.nodes.map(({name:repo, owner:{login:owner}}) => ({repo, owner})) ?? []
|
const repositories = data.user.repositories.nodes.map(({name: repo, owner: {login: owner}}) => ({repo, owner})) ?? []
|
||||||
|
|
||||||
//Get views stats from repositories
|
//Get views stats from repositories
|
||||||
console.debug(`metrics/compute/${login}/plugins > traffic > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > traffic > querying api`)
|
||||||
const views = {count:0, uniques:0}
|
const views = {count: 0, uniques: 0}
|
||||||
const response = [...await Promise.allSettled(repositories.map(({repo, owner}) => (skipped.includes(repo.toLocaleLowerCase())) || (skipped.includes(`${owner}/${repo}`.toLocaleLowerCase())) ? {} : rest.repos.getViews({owner, repo})))].filter(({status}) => status === "fulfilled"
|
const response = [...await Promise.allSettled(repositories.map(({repo, owner}) => (skipped.includes(repo.toLocaleLowerCase())) || (skipped.includes(`${owner}/${repo}`.toLocaleLowerCase())) ? {} : rest.repos.getViews({owner, repo})))].filter(({status}) =>
|
||||||
|
status === "fulfilled"
|
||||||
).map(({value}) => value)
|
).map(({value}) => value)
|
||||||
|
|
||||||
//Compute views
|
//Compute views
|
||||||
console.debug(`metrics/compute/${login}/plugins > traffic > computing stats`)
|
console.debug(`metrics/compute/${login}/plugins > traffic > computing stats`)
|
||||||
response.filter(({data}) => data).map(({data:{count, uniques}}) => (views.count += count, views.uniques += uniques))
|
response.filter(({data}) => data).map(({data: {count, uniques}}) => (views.count += count, views.uniques += uniques))
|
||||||
|
|
||||||
//Results
|
//Results
|
||||||
return {views}
|
return {views}
|
||||||
@@ -31,6 +32,6 @@ export default async function({login, imports, data, rest, q, account}, {enabled
|
|||||||
let message = "An error occured"
|
let message = "An error occured"
|
||||||
if (error.status === 403)
|
if (error.status === 403)
|
||||||
message = "Insufficient token rights"
|
message = "Insufficient token rights"
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {limit, user:username, attachments} = imports.metadata.plugins.tweets.inputs({data, account, q})
|
let {limit, user: username, attachments} = imports.metadata.plugins.tweets.inputs({data, account, q})
|
||||||
|
|
||||||
//Load user profile
|
//Load user profile
|
||||||
console.debug(`metrics/compute/${login}/plugins > tweets > loading twitter profile (@${username})`)
|
console.debug(`metrics/compute/${login}/plugins > tweets > loading twitter profile (@${username})`)
|
||||||
const {data:{data:profile = null}} = await imports.axios.get(`https://api.twitter.com/2/users/by/username/${username}?user.fields=profile_image_url,verified`, {headers:{Authorization:`Bearer ${token}`}})
|
const {data: {data: profile = null}} = await imports.axios.get(`https://api.twitter.com/2/users/by/username/${username}?user.fields=profile_image_url,verified`, {headers: {Authorization: `Bearer ${token}`}})
|
||||||
|
|
||||||
//Load profile image
|
//Load profile image
|
||||||
if (profile?.profile_image_url) {
|
if (profile?.profile_image_url) {
|
||||||
@@ -21,9 +21,9 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
|
|
||||||
//Load tweets
|
//Load tweets
|
||||||
console.debug(`metrics/compute/${login}/plugins > tweets > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > tweets > querying api`)
|
||||||
const {data:{data:tweets = [], includes:{media = []} = {}}} = await imports.axios.get(
|
const {data: {data: tweets = [], includes: {media = []} = {}}} = await imports.axios.get(
|
||||||
`https://api.twitter.com/2/tweets/search/recent?query=from:${username}&tweet.fields=created_at,entities&media.fields=preview_image_url,url,type&expansions=entities.mentions.username,attachments.media_keys`,
|
`https://api.twitter.com/2/tweets/search/recent?query=from:${username}&tweet.fields=created_at,entities&media.fields=preview_image_url,url,type&expansions=entities.mentions.username,attachments.media_keys`,
|
||||||
{headers:{Authorization:`Bearer ${token}`}},
|
{headers: {Authorization: `Bearer ${token}`}},
|
||||||
)
|
)
|
||||||
const medias = new Map(media.map(({media_key, type, url, preview_image_url}) => [media_key, (type === "photo") || (type === "animated_gif") ? url : type === "video" ? preview_image_url : null]))
|
const medias = new Map(media.map(({media_key, type, url, preview_image_url}) => [media_key, (type === "photo") || (type === "animated_gif") ? url : type === "video" ? preview_image_url : null]))
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
await Promise.all(tweets.map(async tweet => {
|
await Promise.all(tweets.map(async tweet => {
|
||||||
//Mentions and urls
|
//Mentions and urls
|
||||||
tweet.mentions = tweet.entities?.mentions?.map(({username}) => username) ?? []
|
tweet.mentions = tweet.entities?.mentions?.map(({username}) => username) ?? []
|
||||||
tweet.urls = new Map(tweet.entities?.urls?.map(({url, display_url:link}) => [url, link]) ?? [])
|
tweet.urls = new Map(tweet.entities?.urls?.map(({url, display_url: link}) => [url, link]) ?? [])
|
||||||
//Attachments
|
//Attachments
|
||||||
if (attachments) {
|
if (attachments) {
|
||||||
//Retrieve linked content
|
//Retrieve linked content
|
||||||
@@ -48,31 +48,31 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
}
|
}
|
||||||
//Medias
|
//Medias
|
||||||
if (tweet.attachments)
|
if (tweet.attachments)
|
||||||
tweet.attachments = await Promise.all(tweet.attachments.media_keys.filter(key => medias.get(key)).map(key => medias.get(key)).map(async url => ({image:await imports.imgb64(url, {height:-1, width:450})})))
|
tweet.attachments = await Promise.all(tweet.attachments.media_keys.filter(key => medias.get(key)).map(key => medias.get(key)).map(async url => ({image: await imports.imgb64(url, {height: -1, width: 450})})))
|
||||||
if (linked) {
|
if (linked) {
|
||||||
const {result:{ogImage, ogSiteName:website, ogTitle:title, ogDescription:description}} = await imports.opengraph({url:linked})
|
const {result: {ogImage, ogSiteName: website, ogTitle: title, ogDescription: description}} = await imports.opengraph({url: linked})
|
||||||
const image = await imports.imgb64(ogImage?.url, {height:-1, width:450, fallback:false})
|
const image = await imports.imgb64(ogImage?.url, {height: -1, width: 450, fallback: false})
|
||||||
if (image) {
|
if (image) {
|
||||||
if (tweet.attachments)
|
if (tweet.attachments)
|
||||||
tweet.attachments.unshift([{image, title, description, website}])
|
tweet.attachments.unshift([{image, title, description, website}])
|
||||||
else
|
else
|
||||||
tweet.attachments = [{image, title, description, website}]
|
tweet.attachments = [{image, title, description, website}]
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
tweet.text = `${tweet.text}\n${linked}`
|
tweet.text = `${tweet.text}\n${linked}`
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
else {
|
||||||
tweet.attachments = null
|
tweet.attachments = null
|
||||||
|
}
|
||||||
|
|
||||||
//Format text
|
//Format text
|
||||||
console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`)
|
console.debug(`metrics/compute/${login}/plugins > tweets > formatting tweet ${tweet.id}`)
|
||||||
tweet.createdAt = `${imports.format.date(tweet.created_at, {time:true})} on ${imports.format.date(tweet.created_at, {date:true})}`
|
tweet.createdAt = `${imports.format.date(tweet.created_at, {time: true})} on ${imports.format.date(tweet.created_at, {date: true})}`
|
||||||
tweet.text = imports.htmlescape(
|
tweet.text = imports.htmlescape(
|
||||||
//Escape tags
|
//Escape tags
|
||||||
imports.htmlescape(tweet.text, {"<":true, ">":true})
|
imports.htmlescape(tweet.text, {"<": true, ">": true})
|
||||||
//Mentions
|
//Mentions
|
||||||
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), '<span class="mention">@$1</span>')
|
.replace(new RegExp(`@(${tweet.mentions.join("|")})`, "gi"), '<span class="mention">@$1</span>')
|
||||||
//Hashtags (this regex comes from the twitter source code)
|
//Hashtags (this regex comes from the twitter source code)
|
||||||
@@ -84,12 +84,12 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
.replace(/\n/g, "<br/>")
|
.replace(/\n/g, "<br/>")
|
||||||
//Links
|
//Links
|
||||||
.replace(new RegExp(`${tweet.urls.size ? "" : "noop^"}(${[...tweet.urls.keys()].map(url => `(?:${url})`).join("|")})`, "gi"), (_, url) => `<a href="${url}" class="link">${tweet.urls.get(url)}</a>`),
|
.replace(new RegExp(`${tweet.urls.size ? "" : "noop^"}(${[...tweet.urls.keys()].map(url => `(?:${url})`).join("|")})`, "gi"), (_, url) => `<a href="${url}" class="link">${tweet.urls.get(url)}</a>`),
|
||||||
{"&":true},
|
{"&": true},
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
return {username, profile, list:tweets}
|
return {username, profile, list: tweets}
|
||||||
}
|
}
|
||||||
//Handle errors
|
//Handle errors
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -100,6 +100,6 @@ export default async function({login, imports, data, q, account}, {enabled = fal
|
|||||||
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
message = `API returned ${status}${description ? ` (${description})` : ""}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
}
|
}
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,30 +7,30 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
return null
|
return null
|
||||||
|
|
||||||
//Load inputs
|
//Load inputs
|
||||||
let {sections, days, limit, url, user, "languages.other":others} = imports.metadata.plugins.wakatime.inputs({data, account, q})
|
let {sections, days, limit, url, user, "languages.other": others} = imports.metadata.plugins.wakatime.inputs({data, account, q})
|
||||||
if (!limit)
|
if (!limit)
|
||||||
limit = void limit
|
limit = void limit
|
||||||
const range = {
|
const range = {
|
||||||
"7":"last_7_days",
|
"7": "last_7_days",
|
||||||
"30":"last_30_days",
|
"30": "last_30_days",
|
||||||
"180":"last_6_months",
|
"180": "last_6_months",
|
||||||
"365":"last_year",
|
"365": "last_year",
|
||||||
}[days] ?? "last_7_days"
|
}[days] ?? "last_7_days"
|
||||||
|
|
||||||
//Querying api and format result (https://wakatime.com/developers#stats)
|
//Querying api and format result (https://wakatime.com/developers#stats)
|
||||||
console.debug(`metrics/compute/${login}/plugins > wakatime > querying api`)
|
console.debug(`metrics/compute/${login}/plugins > wakatime > querying api`)
|
||||||
const {data:{data:stats}} = await imports.axios.get(`${url}/api/v1/users/${user}/stats/${range}?api_key=${token}`)
|
const {data: {data: stats}} = await imports.axios.get(`${url}/api/v1/users/${user}/stats/${range}?api_key=${token}`)
|
||||||
const result = {
|
const result = {
|
||||||
sections,
|
sections,
|
||||||
days,
|
days,
|
||||||
time:{
|
time: {
|
||||||
total:(others ? stats.total_seconds_including_other_language : stats.total_seconds) / (60 * 60),
|
total: (others ? stats.total_seconds_including_other_language : stats.total_seconds) / (60 * 60),
|
||||||
daily:(others ? stats.daily_average_including_other_language : stats.daily_average) / (60 * 60),
|
daily: (others ? stats.daily_average_including_other_language : stats.daily_average) / (60 * 60),
|
||||||
},
|
},
|
||||||
projects:stats.projects?.map(({name, percent, total_seconds:total}) => ({name, percent:percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
projects: stats.projects?.map(({name, percent, total_seconds: total}) => ({name, percent: percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
||||||
languages:stats.languages?.map(({name, percent, total_seconds:total}) => ({name, percent:percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
languages: stats.languages?.map(({name, percent, total_seconds: total}) => ({name, percent: percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
||||||
os:stats.operating_systems?.map(({name, percent, total_seconds:total}) => ({name, percent:percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
os: stats.operating_systems?.map(({name, percent, total_seconds: total}) => ({name, percent: percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
||||||
editors:stats.editors?.map(({name, percent, total_seconds:total}) => ({name, percent:percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
editors: stats.editors?.map(({name, percent, total_seconds: total}) => ({name, percent: percent / 100, total})).sort((a, b) => b.percent - a.percent).slice(0, limit),
|
||||||
}
|
}
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
@@ -44,6 +44,6 @@ export default async function({login, q, imports, data, account}, {enabled = fal
|
|||||||
message = `API returned ${status}`
|
message = `API returned ${status}`
|
||||||
error = error.response?.data ?? null
|
error = error.response?.data ?? null
|
||||||
}
|
}
|
||||||
throw {error:{message, instance:error}}
|
throw {error: {message, instance: error}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,48 +6,48 @@ export default async function(_, {data}, {imports}) {
|
|||||||
const {user, computed, plugins} = data
|
const {user, computed, plugins} = data
|
||||||
Object.assign(data, {
|
Object.assign(data, {
|
||||||
//Base
|
//Base
|
||||||
NAME:user.name,
|
NAME: user.name,
|
||||||
LOGIN:user.login,
|
LOGIN: user.login,
|
||||||
REGISTRATION_DATE:user.createdAt,
|
REGISTRATION_DATE: user.createdAt,
|
||||||
REGISTERED_YEARS:computed.registered.years,
|
REGISTERED_YEARS: computed.registered.years,
|
||||||
LOCATION:user.location,
|
LOCATION: user.location,
|
||||||
WEBSITE:user.websiteUrl,
|
WEBSITE: user.websiteUrl,
|
||||||
REPOSITORIES:user.repositories.totalCount,
|
REPOSITORIES: user.repositories.totalCount,
|
||||||
REPOSITORIES_DISK_USAGE:user.repositories.totalDiskUsage,
|
REPOSITORIES_DISK_USAGE: user.repositories.totalDiskUsage,
|
||||||
PACKAGES:user.packages.totalCount,
|
PACKAGES: user.packages.totalCount,
|
||||||
STARRED:user.starredRepositories.totalCount,
|
STARRED: user.starredRepositories.totalCount,
|
||||||
WATCHING:user.watching.totalCount,
|
WATCHING: user.watching.totalCount,
|
||||||
SPONSORING:user.sponsorshipsAsSponsor.totalCount,
|
SPONSORING: user.sponsorshipsAsSponsor.totalCount,
|
||||||
SPONSORS:user.sponsorshipsAsMaintainer.totalCount,
|
SPONSORS: user.sponsorshipsAsMaintainer.totalCount,
|
||||||
REPOSITORIES_CONTRIBUTED_TO:user.repositoriesContributedTo.totalCount,
|
REPOSITORIES_CONTRIBUTED_TO: user.repositoriesContributedTo.totalCount,
|
||||||
COMMITS:computed.commits,
|
COMMITS: computed.commits,
|
||||||
COMMITS_PUBLIC:user.contributionsCollection.totalCommitContributions,
|
COMMITS_PUBLIC: user.contributionsCollection.totalCommitContributions,
|
||||||
COMMITS_PRIVATE:user.contributionsCollection.restrictedContributionsCount,
|
COMMITS_PRIVATE: user.contributionsCollection.restrictedContributionsCount,
|
||||||
ISSUES:user.contributionsCollection.totalIssueContributions,
|
ISSUES: user.contributionsCollection.totalIssueContributions,
|
||||||
PULL_REQUESTS:user.contributionsCollection.totalPullRequestContributions,
|
PULL_REQUESTS: user.contributionsCollection.totalPullRequestContributions,
|
||||||
PULL_REQUESTS_REVIEWS:user.contributionsCollection.totalPullRequestReviewContributions,
|
PULL_REQUESTS_REVIEWS: user.contributionsCollection.totalPullRequestReviewContributions,
|
||||||
FOLLOWERS:user.followers.totalCount,
|
FOLLOWERS: user.followers.totalCount,
|
||||||
FOLLOWING:user.following.totalCount,
|
FOLLOWING: user.following.totalCount,
|
||||||
ISSUE_COMMENTS:user.issueComments.totalCount,
|
ISSUE_COMMENTS: user.issueComments.totalCount,
|
||||||
ORGANIZATIONS:user.organizations.totalCount,
|
ORGANIZATIONS: user.organizations.totalCount,
|
||||||
WATCHERS:computed.repositories.watchers,
|
WATCHERS: computed.repositories.watchers,
|
||||||
STARGAZERS:computed.repositories.stargazers,
|
STARGAZERS: computed.repositories.stargazers,
|
||||||
FORKS:computed.repositories.forks,
|
FORKS: computed.repositories.forks,
|
||||||
RELEASES:computed.repositories.releases,
|
RELEASES: computed.repositories.releases,
|
||||||
VERSION:data.meta.version,
|
VERSION: data.meta.version,
|
||||||
//Lines
|
//Lines
|
||||||
LINES_ADDED:plugins.lines?.added ?? 0,
|
LINES_ADDED: plugins.lines?.added ?? 0,
|
||||||
LINES_DELETED:plugins.lines?.deleted ?? 0,
|
LINES_DELETED: plugins.lines?.deleted ?? 0,
|
||||||
//Gists
|
//Gists
|
||||||
GISTS:plugins.gists?.totalCount ?? 0,
|
GISTS: plugins.gists?.totalCount ?? 0,
|
||||||
GISTS_STARGAZERS:plugins.gists?.stargazers ?? 0,
|
GISTS_STARGAZERS: plugins.gists?.stargazers ?? 0,
|
||||||
//Languages
|
//Languages
|
||||||
LANGUAGES:plugins.languages?.favorites?.map(({name, value, size, color}) => ({name, value, size, color})) ?? [],
|
LANGUAGES: plugins.languages?.favorites?.map(({name, value, size, color}) => ({name, value, size, color})) ?? [],
|
||||||
//Posts
|
//Posts
|
||||||
POSTS:plugins.posts?.list ?? [],
|
POSTS: plugins.posts?.list ?? [],
|
||||||
//Tweets
|
//Tweets
|
||||||
TWEETS:plugins.tweets?.list ?? [],
|
TWEETS: plugins.tweets?.list ?? [],
|
||||||
//Topics
|
//Topics
|
||||||
TOPICS:plugins.topics?.list ?? [],
|
TOPICS: plugins.topics?.list ?? [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ export default async function({login, q}, {data, rest, graphql, queries, account
|
|||||||
const {repo} = q
|
const {repo} = q
|
||||||
if (!repo) {
|
if (!repo) {
|
||||||
console.debug(`metrics/compute/${login}/${repo} > error, repo was undefined`)
|
console.debug(`metrics/compute/${login}/${repo} > error, repo was undefined`)
|
||||||
data.errors.push({error:{message:'You must pass a "repo" argument to use this template'}})
|
data.errors.push({error: {message: 'You must pass a "repo" argument to use this template'}})
|
||||||
return imports.plugins.core(...arguments)
|
return imports.plugins.core(...arguments)
|
||||||
}
|
}
|
||||||
console.debug(`metrics/compute/${login}/${repo} > switching to mode ${account}`)
|
console.debug(`metrics/compute/${login}/${repo} > switching to mode ${account}`)
|
||||||
|
|
||||||
//Retrieving single repository
|
//Retrieving single repository
|
||||||
console.debug(`metrics/compute/${login}/${repo} > retrieving single repository ${repo}`)
|
console.debug(`metrics/compute/${login}/${repo} > retrieving single repository ${repo}`)
|
||||||
const {[account === "bypass" ? "user" : account]:{repository}} = await graphql(queries.base.repository({login, repo, account}))
|
const {[account === "bypass" ? "user" : account]: {repository}} = await graphql(queries.base.repository({login, repo, account}))
|
||||||
data.user.repositories.nodes = [repository]
|
data.user.repositories.nodes = [repository]
|
||||||
data.repo = repository
|
data.repo = repository
|
||||||
|
|
||||||
//Contributors and sponsors
|
//Contributors and sponsors
|
||||||
data.repo.contributors = {totalCount:(await rest.repos.listContributors({owner:data.repo.owner.login, repo})).data.length}
|
data.repo.contributors = {totalCount: (await rest.repos.listContributors({owner: data.repo.owner.login, repo})).data.length}
|
||||||
data.repo.sponsorshipsAsMaintainer = data.user.sponsorshipsAsMaintainer
|
data.repo.sponsorshipsAsMaintainer = data.user.sponsorshipsAsMaintainer
|
||||||
|
|
||||||
//Get commit activity
|
//Get commit activity
|
||||||
@@ -25,7 +25,7 @@ export default async function({login, q}, {data, rest, graphql, queries, account
|
|||||||
for (let page = 1; page < 100; page++) {
|
for (let page = 1; page < 100; page++) {
|
||||||
console.debug(`metrics/compute/${login}/${repo} > loading page ${page}`)
|
console.debug(`metrics/compute/${login}/${repo} > loading page ${page}`)
|
||||||
try {
|
try {
|
||||||
const {data} = await rest.repos.listCommits({owner:login, repo, per_page:100, page})
|
const {data} = await rest.repos.listCommits({owner: login, repo, per_page: 100, page})
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
console.debug(`metrics/compute/${login}/${repo} > no more page to load`)
|
console.debug(`metrics/compute/${login}/${repo} > no more page to load`)
|
||||||
break
|
break
|
||||||
@@ -58,16 +58,16 @@ export default async function({login, q}, {data, rest, graphql, queries, account
|
|||||||
calendar.splice(days)
|
calendar.splice(days)
|
||||||
const max = Math.max(...calendar)
|
const max = Math.max(...calendar)
|
||||||
//Override contributions calendar
|
//Override contributions calendar
|
||||||
data.user.calendar.contributionCalendar.weeks = calendar.map(commit => ({contributionDays:{color:commit ? `var(--color-calendar-graph-day-L${Math.ceil(commit / max / 0.25)}-bg)` : "var(--color-calendar-graph-day-bg)"}}))
|
data.user.calendar.contributionCalendar.weeks = calendar.map(commit => ({contributionDays: {color: commit ? `var(--color-calendar-graph-day-L${Math.ceil(commit / max / 0.25)}-bg)` : "var(--color-calendar-graph-day-bg)"}}))
|
||||||
|
|
||||||
//Override plugins parameters
|
//Override plugins parameters
|
||||||
q["projects.limit"] = 0
|
q["projects.limit"] = 0
|
||||||
|
|
||||||
//Fetching users count if it's an action
|
//Fetching users count if it's an action
|
||||||
try {
|
try {
|
||||||
if (await rest.repos.getContent({owner:login, repo, path:"action.yml"})) {
|
if (await rest.repos.getContent({owner: login, repo, path: "action.yml"})) {
|
||||||
console.debug(`metrics/compute/${login}/${repo} > this repository seems to be a GitHub action, fetching users using code search`)
|
console.debug(`metrics/compute/${login}/${repo} > this repository seems to be a GitHub action, fetching users using code search`)
|
||||||
const {data:{total_count}} = await rest.search.code({q:`uses ${login} ${repo} path:.github/workflows language:YAML`})
|
const {data: {total_count}} = await rest.search.code({q: `uses ${login} ${repo} path:.github/workflows language:YAML`})
|
||||||
data.repo.actionUsersCount = total_count
|
data.repo.actionUsersCount = total_count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ const ejs = require("ejs")
|
|||||||
|
|
||||||
//Github action
|
//Github action
|
||||||
const action = yaml.load(fs.readFileSync(path.join(__dirname, "../action.yml"), "utf8"))
|
const action = yaml.load(fs.readFileSync(path.join(__dirname, "../action.yml"), "utf8"))
|
||||||
action.defaults = Object.fromEntries(Object.entries(action.inputs).map(([key, { default: value }]) => [key, value]))
|
action.defaults = Object.fromEntries(Object.entries(action.inputs).map(([key, {default: value}]) => [key, value]))
|
||||||
action.input = vars => Object.fromEntries([...Object.entries(action.defaults), ...Object.entries(vars)].map(([key, value]) => [`INPUT_${key.toLocaleUpperCase()}`, value]))
|
action.input = vars => Object.fromEntries([...Object.entries(action.defaults), ...Object.entries(vars)].map(([key, value]) => [`INPUT_${key.toLocaleUpperCase()}`, value]))
|
||||||
action.run = async vars =>
|
action.run = async vars =>
|
||||||
await new Promise((solve, reject) => {
|
await new Promise((solve, reject) => {
|
||||||
let [stdout, stderr] = ["", ""]
|
let [stdout, stderr] = ["", ""]
|
||||||
const env = { ...process.env, ...action.input(vars), GITHUB_REPOSITORY: "lowlighter/metrics" }
|
const env = {...process.env, ...action.input(vars), GITHUB_REPOSITORY: "lowlighter/metrics"}
|
||||||
const child = processes.spawn("node", ["source/app/action/index.mjs"], { env })
|
const child = processes.spawn("node", ["source/app/action/index.mjs"], {env})
|
||||||
child.stdout.on("data", data => stdout += data)
|
child.stdout.on("data", data => stdout += data)
|
||||||
child.stderr.on("data", data => stderr += data)
|
child.stderr.on("data", data => stderr += data)
|
||||||
child.on("close", code => {
|
child.on("close", code => {
|
||||||
@@ -33,7 +33,7 @@ web.run = async vars => (await axios(`http://localhost:3000/lowlighter?${new url
|
|||||||
web.start = async () =>
|
web.start = async () =>
|
||||||
new Promise(solve => {
|
new Promise(solve => {
|
||||||
let stdout = ""
|
let stdout = ""
|
||||||
web.instance = processes.spawn("node", ["source/app/web/index.mjs"], { env: { ...process.env, SANDBOX: true } })
|
web.instance = processes.spawn("node", ["source/app/web/index.mjs"], {env: {...process.env, SANDBOX: true}})
|
||||||
web.instance.stdout.on("data", data => (stdout += data, /Server ready !/.test(stdout) ? solve() : null))
|
web.instance.stdout.on("data", data => (stdout += data, /Server ready !/.test(stdout) ? solve() : null))
|
||||||
web.instance.stderr.on("data", data => console.error(`${data}`))
|
web.instance.stderr.on("data", data => console.error(`${data}`))
|
||||||
})
|
})
|
||||||
@@ -58,8 +58,8 @@ placeholder.run = async vars => {
|
|||||||
const config = Object.fromEntries(Object.entries(options).filter(([key]) => /^config[.]/.test(key)))
|
const config = Object.fromEntries(Object.entries(options).filter(([key]) => /^config[.]/.test(key)))
|
||||||
const base = Object.fromEntries(Object.entries(options).filter(([key]) => /^base[.]/.test(key)))
|
const base = Object.fromEntries(Object.entries(options).filter(([key]) => /^base[.]/.test(key)))
|
||||||
return typeof await placeholder({
|
return typeof await placeholder({
|
||||||
templates: { selected: vars.template },
|
templates: {selected: vars.template},
|
||||||
plugins: { enabled: { ...enabled, base }, options },
|
plugins: {enabled: {...enabled, base}, options},
|
||||||
config,
|
config,
|
||||||
version: "TEST",
|
version: "TEST",
|
||||||
user: "lowlighter",
|
user: "lowlighter",
|
||||||
@@ -70,7 +70,7 @@ placeholder.run = async vars => {
|
|||||||
//Setup
|
//Setup
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
//Clean community template
|
//Clean community template
|
||||||
await fs.promises.rm(path.join(__dirname, "../source/templates/@classic"), { recursive: true, force: true })
|
await fs.promises.rm(path.join(__dirname, "../source/templates/@classic"), {recursive: true, force: true})
|
||||||
//Start web instance
|
//Start web instance
|
||||||
await web.start()
|
await web.start()
|
||||||
})
|
})
|
||||||
@@ -79,7 +79,7 @@ afterAll(async () => {
|
|||||||
//Stop web instance
|
//Stop web instance
|
||||||
await web.stop()
|
await web.stop()
|
||||||
//Clean community template
|
//Clean community template
|
||||||
await fs.promises.rm(path.join(__dirname, "../source/templates/@classic"), { recursive: true, force: true })
|
await fs.promises.rm(path.join(__dirname, "../source/templates/@classic"), {recursive: true, force: true})
|
||||||
})
|
})
|
||||||
|
|
||||||
//Load metadata (as jest doesn't support ESM modules, we use this dirty hack)
|
//Load metadata (as jest doesn't support ESM modules, we use this dirty hack)
|
||||||
@@ -98,11 +98,11 @@ for (const type of ["plugins", "templates"]) {
|
|||||||
for (const name in metadata[type]) {
|
for (const name in metadata[type]) {
|
||||||
const cases = yaml
|
const cases = yaml
|
||||||
.load(fs.readFileSync(path.join(__dirname, "../tests/cases", `${name}.${type.replace(/s$/, "")}.yml`), "utf8"))
|
.load(fs.readFileSync(path.join(__dirname, "../tests/cases", `${name}.${type.replace(/s$/, "")}.yml`), "utf8"))
|
||||||
?.map(({ name: test, with: inputs, modes = [], timeout }) => {
|
?.map(({name: test, with: inputs, modes = [], timeout}) => {
|
||||||
const skip = new Set(Object.entries(metadata.templates).filter(([_, { readme: { compatibility } }]) => !compatibility[name]).map(([template]) => template))
|
const skip = new Set(Object.entries(metadata.templates).filter(([_, {readme: {compatibility}}]) => !compatibility[name]).map(([template]) => template))
|
||||||
if (!(metadata[type][name].supports?.includes("repository")))
|
if (!(metadata[type][name].supports?.includes("repository")))
|
||||||
skip.add("repository")
|
skip.add("repository")
|
||||||
return [test, inputs, { skip: [...skip], modes, timeout }]
|
return [test, inputs, {skip: [...skip], modes, timeout}]
|
||||||
}) ?? []
|
}) ?? []
|
||||||
tests.push(...cases)
|
tests.push(...cases)
|
||||||
}
|
}
|
||||||
@@ -113,13 +113,13 @@ describe("GitHub Action", () =>
|
|||||||
describe.each([
|
describe.each([
|
||||||
["classic", {}],
|
["classic", {}],
|
||||||
["terminal", {}],
|
["terminal", {}],
|
||||||
["repository", { repo: "metrics" }],
|
["repository", {repo: "metrics"}],
|
||||||
])("Template : %s", (template, query) => {
|
])("Template : %s", (template, query) => {
|
||||||
for (const [name, input, { skip = [], modes = [], timeout } = {}] of tests) {
|
for (const [name, input, {skip = [], modes = [], timeout} = {}] of tests) {
|
||||||
if ((skip.includes(template)) || ((modes.length) && (!modes.includes("action"))))
|
if ((skip.includes(template)) || ((modes.length) && (!modes.includes("action"))))
|
||||||
test.skip(name, () => null)
|
test.skip(name, () => null)
|
||||||
else
|
else
|
||||||
test(name, async () => expect(await action.run({ template, base: "", query: JSON.stringify(query), plugins_errors_fatal: true, dryrun: true, use_mocked_data: true, verify: true, ...input })).toBe(true), timeout)
|
test(name, async () => expect(await action.run({template, base: "", query: JSON.stringify(query), plugins_errors_fatal: true, dryrun: true, use_mocked_data: true, verify: true, ...input})).toBe(true), timeout)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -127,13 +127,13 @@ describe("Web instance", () =>
|
|||||||
describe.each([
|
describe.each([
|
||||||
["classic", {}],
|
["classic", {}],
|
||||||
["terminal", {}],
|
["terminal", {}],
|
||||||
["repository", { repo: "metrics" }],
|
["repository", {repo: "metrics"}],
|
||||||
])("Template : %s", (template, query) => {
|
])("Template : %s", (template, query) => {
|
||||||
for (const [name, input, { skip = [], modes = [], timeout } = {}] of tests) {
|
for (const [name, input, {skip = [], modes = [], timeout} = {}] of tests) {
|
||||||
if ((skip.includes(template)) || ((modes.length) && (!modes.includes("web"))))
|
if ((skip.includes(template)) || ((modes.length) && (!modes.includes("web"))))
|
||||||
test.skip(name, () => null)
|
test.skip(name, () => null)
|
||||||
else
|
else
|
||||||
test(name, async () => expect(await web.run({ template, base: 0, ...query, plugins_errors_fatal: true, verify: true, ...input })).toBe(true), timeout)
|
test(name, async () => expect(await web.run({template, base: 0, ...query, plugins_errors_fatal: true, verify: true, ...input})).toBe(true), timeout)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -142,10 +142,10 @@ describe("Web instance (placeholder)", () =>
|
|||||||
["classic", {}],
|
["classic", {}],
|
||||||
["terminal", {}],
|
["terminal", {}],
|
||||||
])("Template : %s", (template, query) => {
|
])("Template : %s", (template, query) => {
|
||||||
for (const [name, input, { skip = [], modes = [], timeout } = {}] of tests) {
|
for (const [name, input, {skip = [], modes = [], timeout} = {}] of tests) {
|
||||||
if ((skip.includes(template)) || ((modes.length) && (!modes.includes("placeholder"))))
|
if ((skip.includes(template)) || ((modes.length) && (!modes.includes("placeholder"))))
|
||||||
test.skip(name, () => null)
|
test.skip(name, () => null)
|
||||||
else
|
else
|
||||||
test(name, async () => expect(await placeholder.run({ template, base: 0, ...query, ...input })).toBe(true), timeout)
|
test(name, async () => expect(await placeholder.run({template, base: 0, ...query, ...input})).toBe(true), timeout)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Last.fm api
|
//Last.fm api
|
||||||
if (/^https:..ws.audioscrobbler.com.*$/.test(url)) {
|
if (/^https:..ws.audioscrobbler.com.*$/.test(url)) {
|
||||||
//Get recently played tracks
|
//Get recently played tracks
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url }) {
|
export default function({faker, url}) {
|
||||||
//Last.fm api
|
//Last.fm api
|
||||||
if (/^https:..testapp.herokuapp.com.*$/.test(url)) {
|
if (/^https:..testapp.herokuapp.com.*$/.test(url)) {
|
||||||
//Get Nightscout Data
|
//Get Nightscout Data
|
||||||
@@ -12,8 +12,8 @@ export default function({ faker, url }) {
|
|||||||
device: "xDrip-DexcomG5",
|
device: "xDrip-DexcomG5",
|
||||||
date: lastInterval,
|
date: lastInterval,
|
||||||
dateString: new Date(lastInterval).toISOString(),
|
dateString: new Date(lastInterval).toISOString(),
|
||||||
sgv: faker.datatype.number({ min: 40, max: 400 }),
|
sgv: faker.datatype.number({min: 40, max: 400}),
|
||||||
delta: faker.datatype.number({ min: -10, max: 10 }),
|
delta: faker.datatype.number({min: -10, max: 10}),
|
||||||
direction: faker.random.arrayElement(["SingleUp", "DoubleUp", "FortyFiveUp", "Flat", "FortyFiveDown", "SingleDown", "DoubleDown"]),
|
direction: faker.random.arrayElement(["SingleUp", "DoubleUp", "FortyFiveUp", "Flat", "FortyFiveDown", "SingleDown", "DoubleDown"]),
|
||||||
type: "sgv",
|
type: "sgv",
|
||||||
filtered: 0,
|
filtered: 0,
|
||||||
@@ -21,7 +21,7 @@ export default function({ faker, url }) {
|
|||||||
rssi: 100,
|
rssi: 100,
|
||||||
noise: 1,
|
noise: 1,
|
||||||
sysTime: new Date(lastInterval).toISOString(),
|
sysTime: new Date(lastInterval).toISOString(),
|
||||||
utcOffset: faker.datatype.number({ min: -12, max: 14 }) * 60,
|
utcOffset: faker.datatype.number({min: -12, max: 14}) * 60,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Tested url
|
//Tested url
|
||||||
const tested = url.match(/&url=(?<tested>.*?)(?:&|$)/)?.groups?.tested ?? faker.internet.url()
|
const tested = url.match(/&url=(?<tested>.*?)(?:&|$)/)?.groups?.tested ?? faker.internet.url()
|
||||||
//Pagespeed api
|
//Pagespeed api
|
||||||
@@ -43,12 +43,12 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
maxPotentialFID: faker.datatype.number(500),
|
maxPotentialFID: faker.datatype.number(500),
|
||||||
observedLoad: faker.datatype.number(500),
|
observedLoad: faker.datatype.number(500),
|
||||||
firstMeaningfulPaint: faker.datatype.number(500),
|
firstMeaningfulPaint: faker.datatype.number(500),
|
||||||
observedCumulativeLayoutShift: faker.datatype.float({ max: 1 }),
|
observedCumulativeLayoutShift: faker.datatype.float({max: 1}),
|
||||||
observedSpeedIndex: faker.datatype.number(1000),
|
observedSpeedIndex: faker.datatype.number(1000),
|
||||||
observedSpeedIndexTs: faker.time.recent(),
|
observedSpeedIndexTs: faker.time.recent(),
|
||||||
observedTimeOriginTs: faker.time.recent(),
|
observedTimeOriginTs: faker.time.recent(),
|
||||||
observedLargestContentfulPaint: faker.datatype.number(1000),
|
observedLargestContentfulPaint: faker.datatype.number(1000),
|
||||||
cumulativeLayoutShift: faker.datatype.float({ max: 1 }),
|
cumulativeLayoutShift: faker.datatype.float({max: 1}),
|
||||||
observedFirstPaintTs: faker.time.recent(),
|
observedFirstPaintTs: faker.time.recent(),
|
||||||
observedTraceEndTs: faker.time.recent(),
|
observedTraceEndTs: faker.time.recent(),
|
||||||
largestContentfulPaint: faker.datatype.number(2000),
|
largestContentfulPaint: faker.datatype.number(2000),
|
||||||
@@ -78,22 +78,22 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
"best-practices": {
|
"best-practices": {
|
||||||
id: "best-practices",
|
id: "best-practices",
|
||||||
title: "Best Practices",
|
title: "Best Practices",
|
||||||
score: faker.datatype.float({ max: 1 }),
|
score: faker.datatype.float({max: 1}),
|
||||||
},
|
},
|
||||||
seo: {
|
seo: {
|
||||||
id: "seo",
|
id: "seo",
|
||||||
title: "SEO",
|
title: "SEO",
|
||||||
score: faker.datatype.float({ max: 1 }),
|
score: faker.datatype.float({max: 1}),
|
||||||
},
|
},
|
||||||
accessibility: {
|
accessibility: {
|
||||||
id: "accessibility",
|
id: "accessibility",
|
||||||
title: "Accessibility",
|
title: "Accessibility",
|
||||||
score: faker.datatype.float({ max: 1 }),
|
score: faker.datatype.float({max: 1}),
|
||||||
},
|
},
|
||||||
performance: {
|
performance: {
|
||||||
id: "performance",
|
id: "performance",
|
||||||
title: "Performance",
|
title: "Performance",
|
||||||
score: faker.datatype.float({ max: 1 }),
|
score: faker.datatype.float({max: 1}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Wakatime api
|
//Wakatime api
|
||||||
if (/^https:..api.poopmap.net.*$/.test(url)) {
|
if (/^https:..api.poopmap.net.*$/.test(url)) {
|
||||||
//Get user profile
|
//Get user profile
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Spotify api
|
//Spotify api
|
||||||
if (/^https:..api.spotify.com.*$/.test(url)) {
|
if (/^https:..api.spotify.com.*$/.test(url)) {
|
||||||
//Get recently played tracks
|
//Get recently played tracks
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Stackoverflow api
|
//Stackoverflow api
|
||||||
if (/^https:..api.stackexchange.com.2.2.*$/.test(url)) {
|
if (/^https:..api.stackexchange.com.2.2.*$/.test(url)) {
|
||||||
//Extract user id
|
//Extract user id
|
||||||
@@ -13,7 +13,7 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
data: {
|
data: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
badge_counts: { bronze: faker.datatype.number(500), silver: faker.datatype.number(300), gold: faker.datatype.number(100) },
|
badge_counts: {bronze: faker.datatype.number(500), silver: faker.datatype.number(300), gold: faker.datatype.number(100)},
|
||||||
accept_rate: faker.datatype.number(100),
|
accept_rate: faker.datatype.number(100),
|
||||||
answer_count: faker.datatype.number(1000),
|
answer_count: faker.datatype.number(1000),
|
||||||
question_count: faker.datatype.number(1000),
|
question_count: faker.datatype.number(1000),
|
||||||
@@ -48,7 +48,7 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
data: {
|
data: {
|
||||||
items: new Array(pagesize).fill(null).map(_ => ({
|
items: new Array(pagesize).fill(null).map(_ => ({
|
||||||
tags: new Array(5).fill(null).map(_ => faker.lorem.slug()),
|
tags: new Array(5).fill(null).map(_ => faker.lorem.slug()),
|
||||||
owner: { display_name: faker.internet.userName() },
|
owner: {display_name: faker.internet.userName()},
|
||||||
is_answered: faker.datatype.boolean(),
|
is_answered: faker.datatype.boolean(),
|
||||||
view_count: faker.datatype.number(10000),
|
view_count: faker.datatype.number(10000),
|
||||||
accepted_answer_id: faker.datatype.number(1000000),
|
accepted_answer_id: faker.datatype.number(1000000),
|
||||||
@@ -77,7 +77,7 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
status: 200,
|
status: 200,
|
||||||
data: {
|
data: {
|
||||||
items: new Array(pagesize).fill(null).map(_ => ({
|
items: new Array(pagesize).fill(null).map(_ => ({
|
||||||
owner: { display_name: faker.internet.userName() },
|
owner: {display_name: faker.internet.userName()},
|
||||||
link: faker.internet.url(),
|
link: faker.internet.url(),
|
||||||
is_accepted: faker.datatype.boolean(),
|
is_accepted: faker.datatype.boolean(),
|
||||||
score: faker.datatype.number(1000),
|
score: faker.datatype.number(1000),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Twitter api
|
//Twitter api
|
||||||
if (/^https:..api.twitter.com.*$/.test(url)) {
|
if (/^https:..api.twitter.com.*$/.test(url)) {
|
||||||
//Get user profile
|
//Get user profile
|
||||||
@@ -31,7 +31,7 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
created_at: `${faker.date.recent()}`,
|
created_at: `${faker.date.recent()}`,
|
||||||
entities: {
|
entities: {
|
||||||
mentions: [
|
mentions: [
|
||||||
{ start: 22, end: 33, username: "lowlighter" },
|
{start: 22, end: 33, username: "lowlighter"},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
text: "Checkout metrics from @lowlighter ! #GitHub",
|
text: "Checkout metrics from @lowlighter ! #GitHub",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Wakatime api
|
//Wakatime api
|
||||||
if (/^https:..wakatime.com.api.v1.users..*.stats.*$/.test(url)) {
|
if (/^https:..wakatime.com.api.v1.users..*.stats.*$/.test(url)) {
|
||||||
//Get user profile
|
//Get user profile
|
||||||
@@ -17,7 +17,7 @@ export default function({ faker, url, options, login = faker.internet.userName()
|
|||||||
percent: 0,
|
percent: 0,
|
||||||
total_seconds: faker.datatype.number(1000000),
|
total_seconds: faker.datatype.number(1000000),
|
||||||
}))
|
}))
|
||||||
results = results.filter(({ name }) => elements.includes(name) ? false : (elements.push(name), true))
|
results = results.filter(({name}) => elements.includes(name) ? false : (elements.push(name), true))
|
||||||
let percents = 100
|
let percents = 100
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
result.percent = 1 + faker.datatype.number(percents - 1)
|
result.percent = 1 + faker.datatype.number(percents - 1)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
//Wakatime api
|
//Wakatime api
|
||||||
if (/^https:..apidojo-yahoo-finance-v1.p.rapidapi.com.stock.v2.*$/.test(url)) {
|
if (/^https:..apidojo-yahoo-finance-v1.p.rapidapi.com.stock.v2.*$/.test(url)) {
|
||||||
//Get company profile
|
//Get company profile
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, body, login = faker.internet.userName() }) {
|
export default function({faker, url, body, login = faker.internet.userName()}) {
|
||||||
if (/^https:..graphql.anilist.co.*$/.test(url)) {
|
if (/^https:..graphql.anilist.co.*$/.test(url)) {
|
||||||
//Initialization and media generator
|
//Initialization and media generator
|
||||||
const { query } = body
|
const {query} = body
|
||||||
const media = ({ type }) => ({
|
const media = ({type}) => ({
|
||||||
title: { romaji: faker.lorem.words(), english: faker.lorem.words(), native: faker.lorem.words() },
|
title: {romaji: faker.lorem.words(), english: faker.lorem.words(), native: faker.lorem.words()},
|
||||||
description: faker.lorem.paragraphs(),
|
description: faker.lorem.paragraphs(),
|
||||||
type,
|
type,
|
||||||
status: faker.random.arrayElement(["FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"]),
|
status: faker.random.arrayElement(["FINISHED", "RELEASING", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"]),
|
||||||
@@ -14,8 +14,8 @@ export default function({ faker, url, body, login = faker.internet.userName() })
|
|||||||
averageScore: faker.datatype.number(100),
|
averageScore: faker.datatype.number(100),
|
||||||
countryOfOrigin: "JP",
|
countryOfOrigin: "JP",
|
||||||
genres: new Array(6).fill(null).map(_ => faker.lorem.word()),
|
genres: new Array(6).fill(null).map(_ => faker.lorem.word()),
|
||||||
coverImage: { medium: null },
|
coverImage: {medium: null},
|
||||||
startDate: { year: faker.date.past(20).getFullYear() },
|
startDate: {year: faker.date.past(20).getFullYear()},
|
||||||
})
|
})
|
||||||
//User statistics query
|
//User statistics query
|
||||||
if (/^query Statistics /.test(query)) {
|
if (/^query Statistics /.test(query)) {
|
||||||
@@ -33,13 +33,13 @@ export default function({ faker, url, body, login = faker.internet.userName() })
|
|||||||
count: faker.datatype.number(1000),
|
count: faker.datatype.number(1000),
|
||||||
minutesWatched: faker.datatype.number(100000),
|
minutesWatched: faker.datatype.number(100000),
|
||||||
episodesWatched: faker.datatype.number(10000),
|
episodesWatched: faker.datatype.number(10000),
|
||||||
genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })),
|
genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})),
|
||||||
},
|
},
|
||||||
manga: {
|
manga: {
|
||||||
count: faker.datatype.number(1000),
|
count: faker.datatype.number(1000),
|
||||||
chaptersRead: faker.datatype.number(100000),
|
chaptersRead: faker.datatype.number(100000),
|
||||||
volumesRead: faker.datatype.number(10000),
|
volumesRead: faker.datatype.number(10000),
|
||||||
genres: new Array(4).fill(null).map(_ => ({ genre: faker.lorem.word() })),
|
genres: new Array(4).fill(null).map(_ => ({genre: faker.lorem.word()})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -58,10 +58,10 @@ export default function({ faker, url, body, login = faker.internet.userName() })
|
|||||||
favourites: {
|
favourites: {
|
||||||
characters: {
|
characters: {
|
||||||
nodes: new Array(2 + faker.datatype.number(16)).fill(null).map(_ => ({
|
nodes: new Array(2 + faker.datatype.number(16)).fill(null).map(_ => ({
|
||||||
name: { full: faker.name.findName(), native: faker.name.findName() },
|
name: {full: faker.name.findName(), native: faker.name.findName()},
|
||||||
image: { medium: null },
|
image: {medium: null},
|
||||||
})),
|
})),
|
||||||
pageInfo: { currentPage: 1, hasNextPage: false },
|
pageInfo: {currentPage: 1, hasNextPage: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -80,8 +80,8 @@ export default function({ faker, url, body, login = faker.internet.userName() })
|
|||||||
User: {
|
User: {
|
||||||
favourites: {
|
favourites: {
|
||||||
[type.toLocaleLowerCase()]: {
|
[type.toLocaleLowerCase()]: {
|
||||||
nodes: new Array(16).fill(null).map(_ => media({ type })),
|
nodes: new Array(16).fill(null).map(_ => media({type})),
|
||||||
pageInfo: { currentPage: 1, hasNextPage: false },
|
pageInfo: {currentPage: 1, hasNextPage: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -92,7 +92,7 @@ export default function({ faker, url, body, login = faker.internet.userName() })
|
|||||||
//Medias query
|
//Medias query
|
||||||
if (/^query Medias /.test(query)) {
|
if (/^query Medias /.test(query)) {
|
||||||
console.debug("metrics/compute/mocks > mocking anilist api result > Medias")
|
console.debug("metrics/compute/mocks > mocking anilist api result > Medias")
|
||||||
const { type } = body.variables
|
const {type} = body.variables
|
||||||
return ({
|
return ({
|
||||||
status: 200,
|
status: 200,
|
||||||
data: {
|
data: {
|
||||||
@@ -100,16 +100,16 @@ export default function({ faker, url, body, login = faker.internet.userName() })
|
|||||||
MediaListCollection: {
|
MediaListCollection: {
|
||||||
lists: [
|
lists: [
|
||||||
{
|
{
|
||||||
name: { ANIME: "Watching", MANGA: "Reading", OTHER: "Completed" }[type],
|
name: {ANIME: "Watching", MANGA: "Reading", OTHER: "Completed"}[type],
|
||||||
isCustomList: false,
|
isCustomList: false,
|
||||||
entries: new Array(16).fill(null).map(_ => ({
|
entries: new Array(16).fill(null).map(_ => ({
|
||||||
status: faker.random.arrayElement(["CURRENT", "PLANNING", "COMPLETED", "DROPPED", "PAUSED", "REPEATING"]),
|
status: faker.random.arrayElement(["CURRENT", "PLANNING", "COMPLETED", "DROPPED", "PAUSED", "REPEATING"]),
|
||||||
progress: faker.datatype.number(100),
|
progress: faker.datatype.number(100),
|
||||||
progressVolumes: null,
|
progressVolumes: null,
|
||||||
score: 0,
|
score: 0,
|
||||||
startedAt: { year: null, month: null, day: null },
|
startedAt: {year: null, month: null, day: null},
|
||||||
completedAt: { year: null, month: null, day: null },
|
completedAt: {year: null, month: null, day: null},
|
||||||
media: media({ type }),
|
media: media({type}),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, body, login = faker.internet.userName() }) {
|
export default function({faker, url, body, login = faker.internet.userName()}) {
|
||||||
if (/^https:..api.hashnode.com.*$/.test(url)) {
|
if (/^https:..api.hashnode.com.*$/.test(url)) {
|
||||||
console.debug(`metrics/compute/mocks > mocking hashnode result > ${url}`)
|
console.debug(`metrics/compute/mocks > mocking hashnode result > ${url}`)
|
||||||
return ({
|
return ({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import urls from "url"
|
import urls from "url"
|
||||||
|
|
||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, body, login = faker.internet.userName() }) {
|
export default function({faker, url, body, login = faker.internet.userName()}) {
|
||||||
if (/^https:..accounts.spotify.com.api.token.*$/.test(url)) {
|
if (/^https:..accounts.spotify.com.api.token.*$/.test(url)) {
|
||||||
//Access token generator
|
//Access token generator
|
||||||
const params = new urls.URLSearchParams(body)
|
const params = new urls.URLSearchParams(body)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, url, options, login = faker.internet.userName() }) {
|
export default function({faker, url, options, login = faker.internet.userName()}) {
|
||||||
if (/^https:..music.youtube.com.youtubei.v1.*$/.test(url)) {
|
if (/^https:..music.youtube.com.youtubei.v1.*$/.test(url)) {
|
||||||
//Get recently played tracks
|
//Get recently played tracks
|
||||||
if (/browse/.test(url)) {
|
if (/browse/.test(url)) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/metrics")
|
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/metrics")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
@@ -22,14 +22,14 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
totalCount: faker.datatype.number(100),
|
totalCount: faker.datatype.number(100),
|
||||||
},
|
},
|
||||||
popular: {
|
popular: {
|
||||||
nodes: [{ stargazers: { totalCount: faker.datatype.number(50000) } }],
|
nodes: [{stargazers: {totalCount: faker.datatype.number(50000)}}],
|
||||||
},
|
},
|
||||||
pullRequests: {
|
pullRequests: {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
createdAt: faker.date.recent(),
|
createdAt: faker.date.recent(),
|
||||||
title: faker.lorem.sentence(),
|
title: faker.lorem.sentence(),
|
||||||
repository: { nameWithOwner: `${faker.internet.userName()}/${faker.lorem.slug()}` },
|
repository: {nameWithOwner: `${faker.internet.userName()}/${faker.lorem.slug()}`},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalCount: faker.datatype.number(50000),
|
totalCount: faker.datatype.number(50000),
|
||||||
@@ -42,29 +42,29 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
pullRequest: {
|
pullRequest: {
|
||||||
title: faker.lorem.sentence(),
|
title: faker.lorem.sentence(),
|
||||||
number: faker.datatype.number(1000),
|
number: faker.datatype.number(1000),
|
||||||
repository: { nameWithOwner: `${faker.internet.userName()}/${faker.lorem.slug()}` },
|
repository: {nameWithOwner: `${faker.internet.userName()}/${faker.lorem.slug()}`},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
totalCount: faker.datatype.number(1000),
|
totalCount: faker.datatype.number(1000),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projects: { totalCount: faker.datatype.number(100) },
|
projects: {totalCount: faker.datatype.number(100)},
|
||||||
packages: { totalCount: faker.datatype.number(100) },
|
packages: {totalCount: faker.datatype.number(100)},
|
||||||
organizations: { nodes: [], totalCount: faker.datatype.number(5) },
|
organizations: {nodes: [], totalCount: faker.datatype.number(5)},
|
||||||
gists: {
|
gists: {
|
||||||
nodes: [{ createdAt: faker.date.recent(), name: faker.lorem.slug() }],
|
nodes: [{createdAt: faker.date.recent(), name: faker.lorem.slug()}],
|
||||||
totalCount: faker.datatype.number(1000),
|
totalCount: faker.datatype.number(1000),
|
||||||
},
|
},
|
||||||
starredRepositories: { totalCount: faker.datatype.number(1000) },
|
starredRepositories: {totalCount: faker.datatype.number(1000)},
|
||||||
followers: { totalCount: faker.datatype.number(10000) },
|
followers: {totalCount: faker.datatype.number(10000)},
|
||||||
following: { totalCount: faker.datatype.number(10000) },
|
following: {totalCount: faker.datatype.number(10000)},
|
||||||
bio: faker.lorem.sentence(),
|
bio: faker.lorem.sentence(),
|
||||||
status: { message: faker.lorem.paragraph() },
|
status: {message: faker.lorem.paragraph()},
|
||||||
sponsorshipsAsSponsor: { totalCount: faker.datatype.number(100) },
|
sponsorshipsAsSponsor: {totalCount: faker.datatype.number(100)},
|
||||||
discussionsStarted: { totalCount: faker.datatype.number(1000) },
|
discussionsStarted: {totalCount: faker.datatype.number(1000)},
|
||||||
discussionsComments: { totalCount: faker.datatype.number(1000) },
|
discussionsComments: {totalCount: faker.datatype.number(1000)},
|
||||||
discussionAnswers: { totalCount: faker.datatype.number(1000) },
|
discussionAnswers: {totalCount: faker.datatype.number(1000)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/metrics")
|
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/metrics")
|
||||||
return ({
|
return ({
|
||||||
repository: { viewerHasStarred: faker.datatype.boolean() },
|
repository: {viewerHasStarred: faker.datatype.boolean()},
|
||||||
viewer: { login },
|
viewer: {login},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/octocat")
|
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/octocat")
|
||||||
return ({
|
return ({
|
||||||
user: { viewerIsFollowing: faker.datatype.boolean() },
|
user: {viewerIsFollowing: faker.datatype.boolean()},
|
||||||
viewer: { login },
|
viewer: {login},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/organizations")
|
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/organizations")
|
||||||
return ({
|
return ({
|
||||||
organization: {
|
organization: {
|
||||||
@@ -22,12 +22,12 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
totalCount: faker.datatype.number(100),
|
totalCount: faker.datatype.number(100),
|
||||||
},
|
},
|
||||||
popular: {
|
popular: {
|
||||||
nodes: [{ stargazers: { totalCount: faker.datatype.number(50000) } }],
|
nodes: [{stargazers: {totalCount: faker.datatype.number(50000)}}],
|
||||||
},
|
},
|
||||||
projects: { totalCount: faker.datatype.number(100) },
|
projects: {totalCount: faker.datatype.number(100)},
|
||||||
packages: { totalCount: faker.datatype.number(100) },
|
packages: {totalCount: faker.datatype.number(100)},
|
||||||
membersWithRole: { totalCount: faker.datatype.number(100) },
|
membersWithRole: {totalCount: faker.datatype.number(100)},
|
||||||
sponsorshipsAsSponsor: { totalCount: faker.datatype.number(100) },
|
sponsorshipsAsSponsor: {totalCount: faker.datatype.number(100)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/ranking")
|
console.debug("metrics/compute/mocks > mocking graphql api result > achievements/ranking")
|
||||||
return ({
|
return ({
|
||||||
repo_rank: { repositoryCount: faker.datatype.number(100000) },
|
repo_rank: {repositoryCount: faker.datatype.number(100000)},
|
||||||
forks_rank: { repositoryCount: faker.datatype.number(100000) },
|
forks_rank: {repositoryCount: faker.datatype.number(100000)},
|
||||||
created_rank: { userCount: faker.datatype.number(100000) },
|
created_rank: {userCount: faker.datatype.number(100000)},
|
||||||
user_rank: { userCount: faker.datatype.number(100000) },
|
user_rank: {userCount: faker.datatype.number(100000)},
|
||||||
repo_total: { repositoryCount: faker.datatype.number(100000) },
|
repo_total: {repositoryCount: faker.datatype.number(100000)},
|
||||||
user_total: { userCount: faker.datatype.number(100000) },
|
user_total: {userCount: faker.datatype.number(100000)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
@@ -8,29 +8,29 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
weeks: [
|
weeks: [
|
||||||
{
|
{
|
||||||
contributionDays: [
|
contributionDays: [
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
contributionDays: [
|
contributionDays: [
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
contributionDays: [
|
contributionDays: [
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
{ color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"]) },
|
{color: faker.random.arrayElement(["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"])},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
packages: { totalCount: faker.datatype.number(10) },
|
packages: {totalCount: faker.datatype.number(10)},
|
||||||
starredRepositories: { totalCount: faker.datatype.number(1000) },
|
starredRepositories: {totalCount: faker.datatype.number(1000)},
|
||||||
watching: { totalCount: faker.datatype.number(100) },
|
watching: {totalCount: faker.datatype.number(100)},
|
||||||
sponsorshipsAsSponsor: { totalCount: faker.datatype.number(10) },
|
sponsorshipsAsSponsor: {totalCount: faker.datatype.number(10)},
|
||||||
sponsorshipsAsMaintainer: { totalCount: faker.datatype.number(10) },
|
sponsorshipsAsMaintainer: {totalCount: faker.datatype.number(10)},
|
||||||
repositoriesContributedTo: { totalCount: faker.datatype.number(100) },
|
repositoriesContributedTo: {totalCount: faker.datatype.number(100)},
|
||||||
followers: { totalCount: faker.datatype.number(1000) },
|
followers: {totalCount: faker.datatype.number(1000)},
|
||||||
following: { totalCount: faker.datatype.number(1000) },
|
following: {totalCount: faker.datatype.number(1000)},
|
||||||
issueComments: { totalCount: faker.datatype.number(1000) },
|
issueComments: {totalCount: faker.datatype.number(1000)},
|
||||||
organizations: { totalCount: faker.datatype.number(10) },
|
organizations: {totalCount: faker.datatype.number(10)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
repositories: { totalCount: faker.datatype.number(100), totalDiskUsage: faker.datatype.number(100000) },
|
repositories: {totalCount: faker.datatype.number(100), totalDiskUsage: faker.datatype.number(100000)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/repositories")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/repositories")
|
||||||
return /after: "MOCKED_CURSOR"/m.test(query)
|
return /after: "MOCKED_CURSOR"/m.test(query)
|
||||||
? ({
|
? ({
|
||||||
@@ -27,30 +27,30 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
name: faker.random.words(),
|
name: faker.random.words(),
|
||||||
watchers: { totalCount: faker.datatype.number(1000) },
|
watchers: {totalCount: faker.datatype.number(1000)},
|
||||||
stargazers: { totalCount: faker.datatype.number(10000) },
|
stargazers: {totalCount: faker.datatype.number(10000)},
|
||||||
owner: { login },
|
owner: {login},
|
||||||
languages: {
|
languages: {
|
||||||
edges: [
|
edges: [
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
issues_open: { totalCount: faker.datatype.number(100) },
|
issues_open: {totalCount: faker.datatype.number(100)},
|
||||||
issues_closed: { totalCount: faker.datatype.number(100) },
|
issues_closed: {totalCount: faker.datatype.number(100)},
|
||||||
pr_open: { totalCount: faker.datatype.number(100) },
|
pr_open: {totalCount: faker.datatype.number(100)},
|
||||||
pr_closed: { totalCount: faker.datatype.number(100) },
|
pr_closed: {totalCount: faker.datatype.number(100)},
|
||||||
pr_merged: { totalCount: faker.datatype.number(100) },
|
pr_merged: {totalCount: faker.datatype.number(100)},
|
||||||
releases: { totalCount: faker.datatype.number(100) },
|
releases: {totalCount: faker.datatype.number(100)},
|
||||||
forkCount: faker.datatype.number(100),
|
forkCount: faker.datatype.number(100),
|
||||||
licenseInfo: { spdxId: "MIT" },
|
licenseInfo: {spdxId: "MIT"},
|
||||||
deployments: { totalCount: faker.datatype.number(100) },
|
deployments: {totalCount: faker.datatype.number(100)},
|
||||||
environments: { totalCount: faker.datatype.number(100) },
|
environments: {totalCount: faker.datatype.number(100)},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,37 +1,37 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/repository")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/repository")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
repository: {
|
repository: {
|
||||||
name: "metrics",
|
name: "metrics",
|
||||||
owner: { login },
|
owner: {login},
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
diskUsage: Math.floor(Math.random() * 10000),
|
diskUsage: Math.floor(Math.random() * 10000),
|
||||||
homepageUrl: faker.internet.url(),
|
homepageUrl: faker.internet.url(),
|
||||||
watchers: { totalCount: faker.datatype.number(1000) },
|
watchers: {totalCount: faker.datatype.number(1000)},
|
||||||
stargazers: { totalCount: faker.datatype.number(10000) },
|
stargazers: {totalCount: faker.datatype.number(10000)},
|
||||||
languages: {
|
languages: {
|
||||||
edges: [
|
edges: [
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
{ size: faker.datatype.number(100000), node: { color: faker.internet.color(), name: faker.lorem.word() } },
|
{size: faker.datatype.number(100000), node: {color: faker.internet.color(), name: faker.lorem.word()}},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
issues_open: { totalCount: faker.datatype.number(100) },
|
issues_open: {totalCount: faker.datatype.number(100)},
|
||||||
issues_closed: { totalCount: faker.datatype.number(100) },
|
issues_closed: {totalCount: faker.datatype.number(100)},
|
||||||
pr_open: { totalCount: faker.datatype.number(100) },
|
pr_open: {totalCount: faker.datatype.number(100)},
|
||||||
pr_closed: { totalCount: faker.datatype.number(100) },
|
pr_closed: {totalCount: faker.datatype.number(100)},
|
||||||
pr_merged: { totalCount: faker.datatype.number(100) },
|
pr_merged: {totalCount: faker.datatype.number(100)},
|
||||||
releases: { totalCount: faker.datatype.number(100) },
|
releases: {totalCount: faker.datatype.number(100)},
|
||||||
forkCount: faker.datatype.number(100),
|
forkCount: faker.datatype.number(100),
|
||||||
licenseInfo: { spdxId: "MIT" },
|
licenseInfo: {spdxId: "MIT"},
|
||||||
deployments: { totalCount: faker.datatype.number(100) },
|
deployments: {totalCount: faker.datatype.number(100)},
|
||||||
environments: { totalCount: faker.datatype.number(100) },
|
environments: {totalCount: faker.datatype.number(100)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > base/user")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > contributors/commit")
|
console.debug("metrics/compute/mocks > mocking graphql api result > contributors/commit")
|
||||||
return ({
|
return ({
|
||||||
repository: {
|
repository: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > discussions/categories")
|
console.debug("metrics/compute/mocks > mocking graphql api result > discussions/categories")
|
||||||
return /after: "MOCKED_CURSOR"/m.test(query)
|
return /after: "MOCKED_CURSOR"/m.test(query)
|
||||||
? ({
|
? ({
|
||||||
@@ -13,7 +13,7 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
: ({
|
: ({
|
||||||
user: {
|
user: {
|
||||||
repositoryDiscussions: {
|
repositoryDiscussions: {
|
||||||
edges: new Array(100).fill(null).map(_ => ({ cursor: "MOCKED_CURSOR" })),
|
edges: new Array(100).fill(null).map(_ => ({cursor: "MOCKED_CURSOR"})),
|
||||||
nodes: new Array(100).fill(null).map(_ => ({
|
nodes: new Array(100).fill(null).map(_ => ({
|
||||||
category: {
|
category: {
|
||||||
emoji: faker.random.arrayElement([":chart_with_upwards_trend:", ":chart_with_downwards_trend:", ":bar_char:"]),
|
emoji: faker.random.arrayElement([":chart_with_upwards_trend:", ":chart_with_downwards_trend:", ":bar_char:"]),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > discussions/comments")
|
console.debug("metrics/compute/mocks > mocking graphql api result > discussions/comments")
|
||||||
return /after: "MOCKED_CURSOR"/m.test(query)
|
return /after: "MOCKED_CURSOR"/m.test(query)
|
||||||
? ({
|
? ({
|
||||||
@@ -13,8 +13,8 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
: ({
|
: ({
|
||||||
user: {
|
user: {
|
||||||
repositoryDiscussionsComments: {
|
repositoryDiscussionsComments: {
|
||||||
edges: new Array(100).fill(null).map(_ => ({ cursor: "MOCKED_CURSOR" })),
|
edges: new Array(100).fill(null).map(_ => ({cursor: "MOCKED_CURSOR"})),
|
||||||
nodes: new Array(100).fill(null).map(_ => ({ upvoteCount: faker.datatype.number(10) })),
|
nodes: new Array(100).fill(null).map(_ => ({upvoteCount: faker.datatype.number(10)})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > discussions/statistics")
|
console.debug("metrics/compute/mocks > mocking graphql api result > discussions/statistics")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
started: { totalCount: faker.datatype.number(1000) },
|
started: {totalCount: faker.datatype.number(1000)},
|
||||||
comments: { totalCount: faker.datatype.number(1000) },
|
comments: {totalCount: faker.datatype.number(1000)},
|
||||||
answers: { totalCount: faker.datatype.number(1000) },
|
answers: {totalCount: faker.datatype.number(1000)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > followup/repository/collaborators")
|
console.debug("metrics/compute/mocks > mocking graphql api result > followup/repository/collaborators")
|
||||||
return ({
|
return ({
|
||||||
repository: {
|
repository: {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > followup/repository")
|
console.debug("metrics/compute/mocks > mocking graphql api result > followup/repository")
|
||||||
return ({
|
return ({
|
||||||
issues_open: { issueCount: faker.datatype.number(100) },
|
issues_open: {issueCount: faker.datatype.number(100)},
|
||||||
issues_drafts: { issueCount: faker.datatype.number(100) },
|
issues_drafts: {issueCount: faker.datatype.number(100)},
|
||||||
issues_skipped: { issueCount: faker.datatype.number(100) },
|
issues_skipped: {issueCount: faker.datatype.number(100)},
|
||||||
issues_closed: { issueCount: faker.datatype.number(100) },
|
issues_closed: {issueCount: faker.datatype.number(100)},
|
||||||
pr_open: { issueCount: faker.datatype.number(100) },
|
pr_open: {issueCount: faker.datatype.number(100)},
|
||||||
pr_drafts: { issueCount: faker.datatype.number(100) },
|
pr_drafts: {issueCount: faker.datatype.number(100)},
|
||||||
pr_closed: { issueCount: faker.datatype.number(100) },
|
pr_closed: {issueCount: faker.datatype.number(100)},
|
||||||
pr_merged: { issueCount: faker.datatype.number(100) },
|
pr_merged: {issueCount: faker.datatype.number(100)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > followup/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > followup/user")
|
||||||
return ({
|
return ({
|
||||||
issues_open: { issueCount: faker.datatype.number(100) },
|
issues_open: {issueCount: faker.datatype.number(100)},
|
||||||
issues_drafts: { issueCount: faker.datatype.number(100) },
|
issues_drafts: {issueCount: faker.datatype.number(100)},
|
||||||
issues_skipped: { issueCount: faker.datatype.number(100) },
|
issues_skipped: {issueCount: faker.datatype.number(100)},
|
||||||
issues_closed: { issueCount: faker.datatype.number(100) },
|
issues_closed: {issueCount: faker.datatype.number(100)},
|
||||||
pr_open: { issueCount: faker.datatype.number(100) },
|
pr_open: {issueCount: faker.datatype.number(100)},
|
||||||
pr_drafts: { issueCount: faker.datatype.number(100) },
|
pr_drafts: {issueCount: faker.datatype.number(100)},
|
||||||
pr_closed: { issueCount: faker.datatype.number(100) },
|
pr_closed: {issueCount: faker.datatype.number(100)},
|
||||||
pr_merged: { issueCount: faker.datatype.number(100) },
|
pr_merged: {issueCount: faker.datatype.number(100)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > gists/default")
|
console.debug("metrics/compute/mocks > mocking graphql api result > gists/default")
|
||||||
return /after: "MOCKED_CURSOR"/m.test(query)
|
return /after: "MOCKED_CURSOR"/m.test(query)
|
||||||
? ({
|
? ({
|
||||||
@@ -23,16 +23,16 @@ export default function({ faker, query, login = faker.internet.userName() }) {
|
|||||||
{
|
{
|
||||||
stargazerCount: faker.datatype.number(10),
|
stargazerCount: faker.datatype.number(10),
|
||||||
isFork: false,
|
isFork: false,
|
||||||
forks: { totalCount: faker.datatype.number(10) },
|
forks: {totalCount: faker.datatype.number(10)},
|
||||||
files: [{ name: faker.system.fileName() }],
|
files: [{name: faker.system.fileName()}],
|
||||||
comments: { totalCount: faker.datatype.number(10) },
|
comments: {totalCount: faker.datatype.number(10)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stargazerCount: faker.datatype.number(10),
|
stargazerCount: faker.datatype.number(10),
|
||||||
isFork: false,
|
isFork: false,
|
||||||
forks: { totalCount: faker.datatype.number(10) },
|
forks: {totalCount: faker.datatype.number(10)},
|
||||||
files: [{ name: faker.system.fileName() }],
|
files: [{name: faker.system.fileName()}],
|
||||||
comments: { totalCount: faker.datatype.number(10) },
|
comments: {totalCount: faker.datatype.number(10)},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > introduction/organization")
|
console.debug("metrics/compute/mocks > mocking graphql api result > introduction/organization")
|
||||||
return ({
|
return ({
|
||||||
organization: {
|
organization: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > introduction/repository")
|
console.debug("metrics/compute/mocks > mocking graphql api result > introduction/repository")
|
||||||
return ({
|
return ({
|
||||||
repository: {
|
repository: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**Mocked data */
|
/**Mocked data */
|
||||||
export default function({ faker, query, login = faker.internet.userName() }) {
|
export default function({faker, query, login = faker.internet.userName()}) {
|
||||||
console.debug("metrics/compute/mocks > mocking graphql api result > introduction/user")
|
console.debug("metrics/compute/mocks > mocking graphql api result > introduction/user")
|
||||||
return ({
|
return ({
|
||||||
user: {
|
user: {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user