Skip to content

Commit 130c74b

Browse files
authored
refactor(app/web): new features (lowlighter#1124) [skip ci]
1 parent 7379fb2 commit 130c74b

File tree

80 files changed

+1301
-1100
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1301
-1100
lines changed

.github/config/label.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
- source/app/action/**
33
- source/app/web/**
44
✨ metrics insights:
5-
- source/app/web/statics/about/**
5+
- source/app/web/statics/insights/**
66

77
🧩 plugins:
88
- source/plugins/**

.github/readme/partials/templated/introduction.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ Generate metrics that can be embedded everywhere, including your GitHub profile
7979
<th colspan="2"><h2>🦑 Try it now!</h2></th>
8080
</tr>
8181
<tr>
82-
<th><a href="https://metrics.lecoq.io">📊 Metrics embed</a></th>
83-
<th><a href="https://metrics.lecoq.io/about">✨ Metrics insights</a></th>
82+
<th><a href="https://metrics.lecoq.io/embed">📊 Metrics embed</a></th>
83+
<th><a href="https://metrics.lecoq.io/insights">✨ Metrics insights</a></th>
8484
</tr>
8585
<tr>
8686
<td align="center">

.github/readme/partials/templated/plugins.community.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ export default async function(
220220
},
221221
//Settings and tokens
222222
{
223-
enabled = false
223+
enabled = false,
224+
extras = false,
224225
} = {}) {
225226
//Plugin execution
226227
try {
@@ -241,7 +242,7 @@ export default async function(
241242
}
242243
//Handle errors
243244
catch (error) {
244-
throw {error:{message:"An error occured", instance:error}}
245+
throw imports.format.error(error)
245246
}
246247
}
247248
```

.github/scripts/preview.mjs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@ const __metrics = paths.join(paths.dirname(url.fileURLToPath(import.meta.url)),
99
const __templates = paths.join(paths.join(__metrics, "source/templates/"))
1010
const __node_modules = paths.join(paths.join(__metrics, "node_modules"))
1111
const __web = paths.join(paths.join(__metrics, "source/app/web/statics"))
12-
const __web_about = paths.join(paths.join(__web, "about"))
1312

1413
const __preview = paths.join(paths.join(__web, "preview"))
1514
const __preview_js = paths.join(__preview, ".js")
1615
const __preview_css = paths.join(__preview, ".css")
1716
const __preview_templates = paths.join(__preview, ".templates")
1817
const __preview_templates_ = paths.join(__preview, ".templates_")
19-
const __preview_about = paths.join(__preview, "about/.statics")
2018

2119
//Extract from web server
2220
const {conf, Templates} = await setup({log: false})
@@ -34,7 +32,6 @@ await fs.mkdir(__preview_js, {recursive: true})
3432
await fs.mkdir(__preview_css, {recursive: true})
3533
await fs.mkdir(__preview_templates, {recursive: true})
3634
await fs.mkdir(__preview_templates_, {recursive: true})
37-
await fs.mkdir(__preview_about, {recursive: true})
3835

3936
//Web
4037
fs.copyFile(paths.join(__web, "index.html"), paths.join(__preview, "index.html"))
@@ -81,9 +78,15 @@ fs.copyFile(paths.join(__node_modules, "clipboard/dist/clipboard.min.js"), paths
8178
//Meta
8279
fs.writeFile(paths.join(__preview, ".version"), JSON.stringify(`${conf.package.version}-preview`))
8380
fs.writeFile(paths.join(__preview, ".hosted"), JSON.stringify({by: "metrics", link: "https://github.com/lowlighter/metrics"}))
84-
//About
85-
fs.copyFile(paths.join(__web, "about", "index.html"), paths.join(__preview, "about", "index.html"))
86-
for (const file of await fs.readdir(__web_about)) {
87-
if (file !== ".statics")
88-
fs.copyFile(paths.join(__web_about, file), paths.join(__preview_about, file))
89-
}
81+
//Insights
82+
for (const insight of ["insights", "about"]) {
83+
const __web_insights = paths.join(paths.join(__web, insight))
84+
const __preview_insights = paths.join(__preview, `${insight}/.statics`)
85+
await fs.mkdir(__preview_insights, {recursive: true})
86+
87+
fs.copyFile(paths.join(__web, insight, "index.html"), paths.join(__preview, insight, "index.html"))
88+
for (const file of await fs.readdir(__web_insights)) {
89+
if (file !== ".statics")
90+
fs.copyFile(paths.join(__web_insights, file), paths.join(__preview_insights, file))
91+
}
92+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
//Setup
2-
export default async function({login, q, imports, data, computed, rest, graphql, queries, account}, {enabled = false} = {}) {
2+
export default async function({login, q, imports, data, computed, rest, graphql, queries, account}, {enabled = false, extras = false} = {}) {
33
//Plugin execution
44
try {
55
//Check if plugin is enabled and requirements are met
6-
if ((!enabled)||(!q.<%= name %>))
6+
if ((!enabled) || (!q.<%= name %>) || (!imports.metadata.plugins.<%= name %>.extras("enabled", {extras})))
77
return null
88
//Results
99
return {}
1010
}
1111
//Handle errors
1212
catch (error) {
13-
throw {error:{message:"An error occured", instance:error}}
13+
throw imports.format.error(error)
1414
}
1515
}

source/app/metrics/index.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
2424
//Initialization
2525
const pending = []
2626
const {queries} = conf
27-
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"] ?? "" : ""}
27+
const extras = {css: imports.metadata.plugins.core.extras("extras_css", {...conf.settings, error:false}) ? q["extras.css"] ?? "" : "", js: imports.metadata.plugins.core.extras("extras_js", {...conf.settings, error:false}) ? q["extras.js"] ?? "" : ""}
2828
const data = {q, animated: true, large: false, base: {}, config: {}, errors: [], plugins: {}, computed: {}, extras, postscripts: []}
2929
const imports = {
3030
plugins: Plugins,
@@ -184,7 +184,7 @@ export default async function metrics({login, q}, {graphql, rest, plugins, conf,
184184
if ((conf.settings?.optimize === true) || (conf.settings?.optimize?.includes?.("svg")))
185185
rendered = await imports.svg.optimize.svg(rendered, q, experimental)
186186
//Verify svg
187-
if (verify) {
187+
if ((verify)&&(imports.metadata.plugins.core.extras("verify", {...conf.settings, error:false}))) {
188188
console.debug(`metrics/compute/${login} > verify SVG`)
189189
let libxmljs = null
190190
try {
@@ -281,9 +281,9 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
281281
console.debug(`metrics/compute/${login} > insights > generating data`)
282282
const result = await metrics.insights({login}, {graphql, rest, conf}, {Plugins, Templates})
283283
const json = JSON.stringify(result)
284-
await page.goto(`${server}/about/${login}?embed=1&localstorage=1`)
284+
await page.goto(`${server}/insights/${login}?embed=1&localstorage=1`)
285285
await page.evaluate(async json => localStorage.setItem("local.metrics", json), json) //eslint-disable-line no-undef
286-
await page.goto(`${server}/about/${login}?embed=1&localstorage=1`)
286+
await page.goto(`${server}/insights/${login}?embed=1&localstorage=1`)
287287
await page.waitForSelector(".container .user", {timeout: 10 * 60 * 1000})
288288

289289
//Rendering
@@ -297,7 +297,7 @@ metrics.insights.output = async function({login, imports, conf}, {graphql, rest,
297297
</head>
298298
<body>
299299
${await page.evaluate(() => document.querySelector("main").outerHTML)}
300-
${(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")}
300+
${(await Promise.all([".css/style.vars.css", ".css/style.css", "insights/.statics/style.css"].map(path => utils.axios.get(`${server}/${path}`)))).map(({data: style}) => `<style>${style}</style>`).join("\n")}
301301
</body>
302302
</html>`
303303
await browser.close()

source/app/metrics/metadata.mjs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
111111
if (account !== "bypass") {
112112
const context = q.repo ? "repository" : account
113113
if (!meta.supports?.includes(context))
114-
throw {error: {message: `Not supported for: ${context}`, instance: new Error()}}
114+
throw {error: {message: `Unsupported context ${context}`, instance: new Error()}}
115115
}
116116
//Special values replacer
117117
const replacer = value => {
@@ -214,33 +214,59 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
214214

215215
//Extra features parser
216216
{
217-
meta.extras = function(input, {extras = {}}) {
218-
//Required permissions
219-
const required = inputs[metadata.to.yaml(input, {name})]?.extras ?? null
220-
if (!required)
221-
return true
222-
console.debug(`metrics/extras > ${name} > ${input} > require [${required}]`)
217+
meta.extras = function(input, {extras = {}, error = true}) {
218+
const key = metadata.to.yaml(input, {name})
219+
try {
220+
//Required permissions
221+
const required = inputs[key]?.extras ?? null
222+
if (!required)
223+
return true
224+
console.debug(`metrics/extras > ${name} > ${key} > require [${required}]`)
225+
226+
//Legacy handling
227+
const enabled = extras?.features ?? extras?.default ?? false
228+
if (typeof enabled === "boolean") {
229+
console.debug(`metrics/extras > ${name} > ${key} > extras features is set to ${enabled}`)
230+
if (!enabled)
231+
throw new Error()
232+
return enabled
233+
}
234+
if (!Array.isArray(required)) {
235+
console.debug(`metrics/extras > ${name} > ${key} > extras is not a permission array, skipping`)
236+
return false
237+
}
223238

224-
//Legacy handling
225-
const enabled = extras?.features ?? extras?.default ?? false
226-
if (typeof enabled === "boolean") {
227-
console.debug(`metrics/extras > ${name} > ${input} > extras features is set to ${enabled}`)
228-
return enabled
229-
}
230-
if (!Array.isArray(required)) {
231-
console.debug(`metrics/extras > ${name} > ${input} > extras is not a permission array, skipping`)
232-
return false
233-
}
239+
//Legacy options handling
240+
if (!Array.isArray(extras.features))
241+
throw new Error(`metrics/extras > ${name} > ${key} > extras.features is not an array`)
242+
if (extras.css) {
243+
console.warn(`metrics/extras > ${name} > ${key} > extras.css is deprecated, use extras.features with "metrics.run.puppeteer.user.css" instead`)
244+
extras.features.push("metrics.run.puppeteer.user.css")
245+
}
246+
if (extras.js) {
247+
console.warn(`metrics/extras > ${name} > ${key} > extras.js is deprecated, use extras.features with "metrics.run.puppeteer.user.js" instead`)
248+
extras.features.push("metrics.run.puppeteer.user.js")
249+
}
250+
if (extras.presets) {
251+
console.warn(`metrics/extras > ${name} > ${key} > extras.presets is deprecated, use extras.features with "metrics.setup.community.presets" instead`)
252+
extras.features.push("metrics.setup.community.presets")
253+
}
234254

235-
//Check permissions
236-
if (!Array.isArray(extras.features))
237-
throw new Error(`metrics/extras > ${name} > ${input} > extras.features is not an array`)
238-
const missing = required.filter(permission => !extras.features.includes(permission))
239-
if (missing.length > 0) {
240-
console.debug(`metrics/extras > ${name} > ${input} > missing permissions [${missing}], skipping`)
241-
return false
255+
//Check permissions
256+
const missing = required.filter(permission => !extras.features.includes(permission))
257+
if (missing.length > 0) {
258+
console.debug(`metrics/extras > ${name} > ${key} > missing permissions [${missing}]`)
259+
throw new Error()
260+
}
261+
return true
262+
}
263+
catch {
264+
if (!error) {
265+
console.debug(`metrics/extras > ${name} > ${key} > skipping (no error mode)`)
266+
return false
267+
}
268+
throw Object.assign(new Error(`Unsupported option "${key}"`), {extras:true})
242269
}
243-
return true
244270
}
245271
}
246272

@@ -576,7 +602,9 @@ metadata.to = {
576602
return name ? key.replace(new RegExp(`^(${name}.)`, "g"), "") : key
577603
},
578604
yaml(key, {name = ""} = {}) {
579-
const parts = [key.replaceAll(".", "_")]
605+
const parts = []
606+
if (key !== "enabled")
607+
parts.unshift(key.replaceAll(".", "_"))
580608
if (name)
581609
parts.unshift((name === "base") ? name : `plugin_${name}`)
582610
return parts.join("_")

source/app/metrics/setup.mjs

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -78,63 +78,68 @@ export default async function({log = true, sandbox = false, community = {}} = {}
7878
logger("metrics/setup > load package.json > success")
7979

8080
//Load community templates
81-
if ((typeof conf.settings.community.templates === "string") && (conf.settings.community.templates.length)) {
82-
logger("metrics/setup > parsing community templates list")
83-
conf.settings.community.templates = [...new Set([...decodeURIComponent(conf.settings.community.templates).split(",").map(v => v.trim().toLocaleLowerCase()).filter(v => v)])]
84-
}
85-
if ((Array.isArray(conf.settings.community.templates)) && (conf.settings.community.templates.length)) {
86-
//Clean remote repository
87-
logger(`metrics/setup > ${conf.settings.community.templates.length} community templates to install`)
88-
await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true})
89-
//Download community templates
90-
for (const template of conf.settings.community.templates) {
91-
try {
92-
//Parse community template
93-
logger(`metrics/setup > load community template ${template}`)
94-
const {repo, branch, name, trust = false} = template.match(/^(?<repo>[\s\S]+?)@(?<branch>[\s\S]+?):(?<name>[\s\S]+?)(?<trust>[+]trust)?$/)?.groups ?? null
95-
const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}`
96-
logger(`metrics/setup > run ${command}`)
97-
//Clone remote repository
98-
processes.execSync(command, {stdio: "ignore"})
99-
//Extract template
100-
logger(`metrics/setup > extract ${name} from ${repo}@${branch}`)
101-
await fs.promises.rm(path.join(__templates, `@${name}`), {recursive: true, force: true})
102-
await fs.promises.rename(path.join(__templates, ".community/source/templates", name), path.join(__templates, `@${name}`))
103-
//JavaScript file
104-
if (trust)
105-
logger(`metrics/setup > keeping @${name}/template.mjs (unsafe mode is enabled)`)
106-
else if (fs.existsSync(path.join(__templates, `@${name}`, "template.mjs"))) {
107-
logger(`metrics/setup > removing @${name}/template.mjs`)
108-
await fs.promises.unlink(path.join(__templates, `@${name}`, "template.mjs"))
109-
const inherit = yaml.load(`${fs.promises.readFile(path.join(__templates, `@${name}`, "metadata.yml"))}`).extends ?? null
110-
if (inherit) {
111-
logger(`metrics/setup > @${name} extends from ${inherit}`)
112-
if (fs.existsSync(path.join(__templates, inherit, "template.mjs"))) {
113-
logger(`metrics/setup > @${name} extended from ${inherit}`)
114-
await fs.promises.copyFile(path.join(__templates, inherit, "template.mjs"), path.join(__templates, `@${name}`, "template.mjs"))
115-
}
116-
else {
117-
logger(`metrics/setup > @${name} could not extends ${inherit} as it does not exist`)
81+
if ((conf.settings.extras?.features?.includes("metrics.setup.community.templates"))||(conf.settings.extras?.features === true)||(conf.settings.extras?.default)) {
82+
if ((typeof conf.settings.community.templates === "string") && (conf.settings.community.templates.length)) {
83+
logger("metrics/setup > parsing community templates list")
84+
conf.settings.community.templates = [...new Set([...decodeURIComponent(conf.settings.community.templates).split(",").map(v => v.trim().toLocaleLowerCase()).filter(v => v)])]
85+
}
86+
if ((Array.isArray(conf.settings.community.templates)) && (conf.settings.community.templates.length)) {
87+
//Clean remote repository
88+
logger(`metrics/setup > ${conf.settings.community.templates.length} community templates to install`)
89+
await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true})
90+
//Download community templates
91+
for (const template of conf.settings.community.templates) {
92+
try {
93+
//Parse community template
94+
logger(`metrics/setup > load community template ${template}`)
95+
const {repo, branch, name, trust = false} = template.match(/^(?<repo>[\s\S]+?)@(?<branch>[\s\S]+?):(?<name>[\s\S]+?)(?<trust>[+]trust)?$/)?.groups ?? null
96+
const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}`
97+
logger(`metrics/setup > run ${command}`)
98+
//Clone remote repository
99+
processes.execSync(command, {stdio: "ignore"})
100+
//Extract template
101+
logger(`metrics/setup > extract ${name} from ${repo}@${branch}`)
102+
await fs.promises.rm(path.join(__templates, `@${name}`), {recursive: true, force: true})
103+
await fs.promises.rename(path.join(__templates, ".community/source/templates", name), path.join(__templates, `@${name}`))
104+
//JavaScript file
105+
if (trust)
106+
logger(`metrics/setup > keeping @${name}/template.mjs (unsafe mode is enabled)`)
107+
else if (fs.existsSync(path.join(__templates, `@${name}`, "template.mjs"))) {
108+
logger(`metrics/setup > removing @${name}/template.mjs`)
109+
await fs.promises.unlink(path.join(__templates, `@${name}`, "template.mjs"))
110+
const inherit = yaml.load(`${fs.promises.readFile(path.join(__templates, `@${name}`, "metadata.yml"))}`).extends ?? null
111+
if (inherit) {
112+
logger(`metrics/setup > @${name} extends from ${inherit}`)
113+
if (fs.existsSync(path.join(__templates, inherit, "template.mjs"))) {
114+
logger(`metrics/setup > @${name} extended from ${inherit}`)
115+
await fs.promises.copyFile(path.join(__templates, inherit, "template.mjs"), path.join(__templates, `@${name}`, "template.mjs"))
116+
}
117+
else {
118+
logger(`metrics/setup > @${name} could not extends ${inherit} as it does not exist`)
119+
}
118120
}
119121
}
122+
else {
123+
logger(`metrics/setup > @${name}/template.mjs does not exist`)
124+
}
125+
126+
//Clean remote repository
127+
logger(`metrics/setup > clean ${repo}@${branch}`)
128+
await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true})
129+
logger(`metrics/setup > loaded community template ${name}`)
120130
}
121-
else {
122-
logger(`metrics/setup > @${name}/template.mjs does not exist`)
131+
catch (error) {
132+
logger(`metrics/setup > failed to load community template ${template}`)
133+
logger(error)
123134
}
124-
125-
//Clean remote repository
126-
logger(`metrics/setup > clean ${repo}@${branch}`)
127-
await fs.promises.rm(path.join(__templates, ".community"), {recursive: true, force: true})
128-
logger(`metrics/setup > loaded community template ${name}`)
129-
}
130-
catch (error) {
131-
logger(`metrics/setup > failed to load community template ${template}`)
132-
logger(error)
133135
}
134136
}
137+
else {
138+
logger("metrics/setup > no community templates to install")
139+
}
135140
}
136141
else {
137-
logger("metrics/setup > no community templates to install")
142+
logger("metrics/setup > community templates are disabled")
138143
}
139144

140145
//Load templates
@@ -188,6 +193,18 @@ export default async function({log = true, sandbox = false, community = {}} = {}
188193
//Load metadata
189194
conf.metadata = await metadata({log})
190195

196+
//Modes
197+
if ((!conf.settings.modes)||(!conf.settings.modes.length))
198+
conf.settings.modes = ["embed", "insights"]
199+
logger(`metrics/setup > setup > enabled modes ${JSON.stringify(conf.settings.modes)}`)
200+
201+
//Allowed outputs formats
202+
if ((!conf.settings.outputs)||(!conf.settings.outputs.length))
203+
conf.settings.outputs = metadata.inputs.config_output.values
204+
else
205+
conf.settings.outputs = conf.settings.outputs.filter(format => metadata.inputs.config_output.values.includes(format))
206+
logger(`metrics/setup > setup > allowed outputs ${JSON.stringify(conf.settings.outputs)}`)
207+
191208
//Store authenticated user
192209
if (conf.settings.token) {
193210
try {

0 commit comments

Comments
 (0)