Feat community templates (#68)

This commit is contained in:
Simon Lecoq
2021-01-19 20:51:51 +01:00
committed by GitHub
parent 9beecade9d
commit d2923797e5
9 changed files with 153 additions and 39 deletions

6
.gitignore vendored
View File

@@ -104,4 +104,8 @@ dist
.tern-port .tern-port
# User settings # User settings
settings.json settings.json
# Community templates
source/templates/.community
source/templates/@*

View File

@@ -630,6 +630,41 @@ The default template is `classic`.
* **N**: Feature is already released, but new ones are available on `@master` * **N**: Feature is already released, but new ones are available on `@master`
* **R**: Repository template (all plugins content will be restricted to related repository) * **R**: Repository template (all plugins content will be restricted to related repository)
<details>
<summary>💬 Using community templates</summary>
🚧 This feature is available as pre-release on @master branch (unstable)
It is possible to use official releases along with custom templates from forked repositories (not necessarily your own).
This can be used to use different layouts, styles colors, etc.
Use `setup_community_templates` option to specify additional external sources in the following format: `user/repo@branch:template`. Templates added this way will be downloaded through git and will be available with the same template name but prefixed with `@`.
For example, to use the `super-metrics` template from `github-user`'s fork, add the following:
```yaml
- uses: lowlighter/metrics@master
with:
# ... other options
template: "@super-metrics"
setup_community_templates: github-user/metrics@master:classic
```
By default, community templates have their `template.mjs` removed and fallback to the one used by `classic` template.
It means that they're restricted to common and plugins data, to prevent malicious code injection and token leaks.
If you really trust a template, it is possible to bypass this behaviour by appending `+trust` at the end of their source like below:
```yaml
- uses: lowlighter/metrics@master
with:
# ... other options
setup_community_templates: github-user/metrics@master:classic+trust
```
To create a new community template, just fork this repository and create a folder in `/source/templates` with the same structure as current templates.
Then, it's just as simple as HTML and CSS with a bit of JavaScript!
</details>
<details> <details>
<summary>💬 Using repository template</summary> <summary>💬 Using repository template</summary>

View File

@@ -38,6 +38,13 @@ inputs:
description: SVG optimization description: SVG optimization
default: yes default: yes
# Setup additional templates from remote repositories (like forks)
# Format is : user/repo@branch:template
# To use a community template, set "template" option to "@template" (where template is the template name)
setup_community_templates:
description: Additional community templates to setup
default: ""
# Timezone used by metrics # Timezone used by metrics
# See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones # See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# Some plugins will use it to calibrate dates # Some plugins will use it to calibrate dates

66
package-lock.json generated
View File

@@ -936,16 +936,16 @@
} }
}, },
"@octokit/openapi-types": { "@octokit/openapi-types": {
"version": "2.2.0", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.3.1.tgz",
"integrity": "sha512-274lNUDonw10kT8wHg8fCcUc1ZjZHbWv0/TbAwb0ojhBQqZYc1cQ/4yqTVTtPMDeZ//g7xVEYe/s3vURkRghPg==" "integrity": "sha512-KTzpRDT07euvbBYbPs121YDqq5DT94nBDFIyogsDhOnWL8yDCHev6myeiPTgS+VLmyUbdNCYu6L/gVj+Bd1q8Q=="
}, },
"@octokit/plugin-paginate-rest": { "@octokit/plugin-paginate-rest": {
"version": "2.7.0", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.7.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.7.1.tgz",
"integrity": "sha512-+zARyncLjt9b0FjqPAbJo4ss7HOlBi1nprq+cPlw5vu2+qjy7WvlXhtXFdRHQbSL1Pt+bfAKaLADEkkvg8sP8w==", "integrity": "sha512-dUsxsEIrBqhlQNfXRhMhXOTQi0SSG38+QWcPGO226HFPFJk44vWukegHfMG3496vLv9T2oT7IuAGssGpcUg5bQ==",
"requires": { "requires": {
"@octokit/types": "^6.0.1" "@octokit/types": "^6.3.1"
} }
}, },
"@octokit/plugin-request-log": { "@octokit/plugin-request-log": {
@@ -954,11 +954,11 @@
"integrity": "sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==" "integrity": "sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg=="
}, },
"@octokit/plugin-rest-endpoint-methods": { "@octokit/plugin-rest-endpoint-methods": {
"version": "4.4.3", "version": "4.5.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.4.3.tgz", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.5.2.tgz",
"integrity": "sha512-qzGV1D8m8pRc3BLcKQIGeCMO2VfzcG5s0l5aXnmguTg6I7/x9sCAUNzhpeIOnHGrDTpF2STqB7duYJm4CxUo3Q==", "integrity": "sha512-JXoDIh+QnzFb6C5ZqIcUzDkn1fLrxawi98ZbvYb9s7Z2CJLITUWpbTAxSgseczEho18pYhamEBRR/h3o3HIXJQ==",
"requires": { "requires": {
"@octokit/types": "^6.1.0", "@octokit/types": "^6.3.2",
"deprecation": "^2.3.1" "deprecation": "^2.3.1"
} }
}, },
@@ -1010,11 +1010,11 @@
} }
}, },
"@octokit/types": { "@octokit/types": {
"version": "6.2.1", "version": "6.3.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.2.1.tgz", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.3.2.tgz",
"integrity": "sha512-jHs9OECOiZxuEzxMZcXmqrEO8GYraHF+UzNVH2ACYh8e/Y7YoT+hUf9ldvVd6zIvWv4p3NdxbQ0xx3ku5BnSiA==", "integrity": "sha512-H6cbnDumWOQJneyNKCBWgnktRqTWcEm6gq2cIS3frtVgpCqB8zguromnjIWJW375btjnxwmbYBTEAEouruZ2Yw==",
"requires": { "requires": {
"@octokit/openapi-types": "^2.2.0", "@octokit/openapi-types": "^2.3.1",
"@types/node": ">= 8" "@types/node": ">= 8"
} }
}, },
@@ -1111,9 +1111,9 @@
} }
}, },
"@types/node": { "@types/node": {
"version": "14.14.20", "version": "14.14.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz",
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==" "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A=="
}, },
"@types/normalize-package-data": { "@types/normalize-package-data": {
"version": "2.4.0", "version": "2.4.0",
@@ -2343,22 +2343,24 @@
} }
}, },
"es-abstract": { "es-abstract": {
"version": "1.18.0-next.1", "version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"requires": { "requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1", "es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1", "function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3", "has": "^1.0.3",
"has-symbols": "^1.0.1", "has-symbols": "^1.0.1",
"is-callable": "^1.2.2", "is-callable": "^1.2.2",
"is-negative-zero": "^2.0.0", "is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1", "is-regex": "^1.1.1",
"object-inspect": "^1.8.0", "object-inspect": "^1.9.0",
"object-keys": "^1.1.1", "object-keys": "^1.1.1",
"object.assign": "^4.1.1", "object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.1", "string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.1" "string.prototype.trimstart": "^1.0.3"
} }
}, },
"es-to-primitive": { "es-to-primitive": {
@@ -5654,9 +5656,9 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
}, },
"parse-json": { "parse-json": {
"version": "5.1.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
@@ -7315,9 +7317,9 @@
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
}, },
"vue-prism-component": { "vue-prism-component": {
"version": "2.0.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/vue-prism-component/-/vue-prism-component-2.0.0.tgz", "resolved": "https://registry.npmjs.org/vue-prism-component/-/vue-prism-component-1.2.0.tgz",
"integrity": "sha512-1ofrL+GCZOv4HqtX5W3EgkhSAgadSeuD8FDTXbwhLy8kS+28RCR8t2S5VTeM9U/peAaXLBpSgRt3J25ao8KTeg==" "integrity": "sha512-0N9CNuQu+36CJpdsZHrhdq7d18oBvjVMjawyKdIr8xuzFWLfdxECZQYbFaYoopPBg3SvkEEMtkhYqdgTQl5Y+A=="
}, },
"w3c-hr-time": { "w3c-hr-time": {
"version": "1.0.2", "version": "1.0.2",

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"start": "node source/app/web/index.mjs", "start": "node source/app/web/index.mjs",
"test": "npx jest", "test": "npx jest",
"upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest colors@latest compression@latest ejs@latest express@latest express-rate-limit@latest image-to-base64@latest memory-cache@latest prismjs@latest puppeteer@latest svgo@latest vue@latest vue-prism-component@latest faker@latest jest@latest js-yaml@latest libxmljs@latest" "upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest colors@latest compression@latest ejs@latest express@latest express-rate-limit@latest image-to-base64@latest memory-cache@latest prismjs@latest puppeteer@latest svgo@latest vue@latest faker@latest jest@latest js-yaml@latest libxmljs@latest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -35,7 +35,7 @@
"puppeteer": "^5.5.0", "puppeteer": "^5.5.0",
"svgo": "^1.3.2", "svgo": "^1.3.2",
"vue": "^2.6.12", "vue": "^2.6.12",
"vue-prism-component": "^2.0.0" "vue-prism-component": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"faker": "^5.1.0", "faker": "^5.1.0",

View File

@@ -12,6 +12,9 @@
"debug":false, "//":"Debug mode", "debug":false, "//":"Debug mode",
"mocked":false, "//":"Use mocked data", "mocked":false, "//":"Use mocked data",
"repositories":100, "//":"Number of repositories to use to compute metrics", "repositories":100, "//":"Number of repositories to use to compute metrics",
"community":{ "//":"Community settings",
"templates":[], "//":"Community templates"
},
"templates":{ "//":"Template configuration", "templates":{ "//":"Template configuration",
"default":"classic", "//":"Default template", "default":"classic", "//":"Default template",
"enabled":[], "//":"Enabled templates, leave empty to enable all templates" "enabled":[], "//":"Enabled templates, leave empty to enable all templates"

View File

@@ -46,8 +46,14 @@
} }
} }
//Pre-Setup
const community = {
templates:input.array("setup_community_templates")
}
info("Setup - community templates", community.templates)
//Load configuration //Load configuration
const {conf, Plugins, Templates} = await setup({log:false, nosettings:true}) const {conf, Plugins, Templates} = await setup({log:false, nosettings:true, community})
info("Setup", "complete") info("Setup", "complete")
info("Version", conf.package.version) info("Version", conf.package.version)

View File

@@ -3,11 +3,13 @@
import path from "path" import path from "path"
import util from "util" import util from "util"
import url from "url" import url from "url"
import processes from "child_process"
const Templates = {} const Templates = {}
const Plugins = {} const Plugins = {}
/** Setup */ /** Setup */
export default async function ({log = true, nosettings = false} = {}) { export default async function ({log = true, nosettings = false, community = {}} = {}) {
//Paths //Paths
const __metrics = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "../..") const __metrics = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "../..")
@@ -49,6 +51,7 @@
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.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}))
@@ -58,6 +61,47 @@
conf.package = JSON.parse(`${await fs.promises.readFile(__package)}`) conf.package = JSON.parse(`${await fs.promises.readFile(__package)}`)
logger(`metrics/setup > load package.json > success`) logger(`metrics/setup > load package.json > success`)
//Load community template
if ((Array.isArray(conf.settings.community.templates))&&(conf.settings.community.templates.length)) {
//Clean remote repository
logger(`metrics/setup > ${conf.settings.community.templates.length} community templates to install`)
await fs.promises.rmdir(path.join(__templates, ".community"), {recursive:true})
//Download community templates
for (const template of conf.settings.community.templates) {
try {
//Parse community template
logger(`metrics/setup > load community template ${template}`)
const {repo, branch, name, trust = false} = template.match(/^(?<repo>[\s\S]+?)@(?<branch>[\s\S]+?):(?<name>[\s\S]+?)(?<trust>[+]trust)?$/)?.groups
const command = `git clone --single-branch --branch ${branch} https://github.com/${repo}.git ${path.join(__templates, ".community")}`
logger(`metrics/setup > run ${command}`)
//Clone remote repository
processes.execSync(command, {stdio:"ignore"})
//Extract template
logger(`metrics/setup > extract ${name} from ${repo}@${branch}`)
await fs.promises.rmdir(path.join(__templates, `@${name}`), {recursive:true})
await fs.promises.rename(path.join(__templates, ".community/source/templates", name), path.join(__templates, `@${name}`))
//JavaScript file
if (trust)
logger(`metrics/setup > keeping @${name}/template.mjs (unsafe mode is enabled)`)
else if (fs.existsSync(path.join(__templates, `@${name}`, "template.mjs"))) {
logger(`metrics/setup > removing @${name}/template.mjs`)
await fs.promises.unlink(path.join(__templates, `@${name}`, "template.mjs"))
}
else
logger(`metrics/setup > @${name}/template.mjs does not exist`)
//Clean remote repository
logger(`metrics/setup > clean ${repo}@${branch}`)
await fs.promises.rmdir(path.join(__templates, ".community"), {recursive:true})
logger(`metrics/setup > loaded community template ${name}`)
} catch (error) {
logger(`metrics/setup > failed to load community template ${template}`)
logger(error)
}
}
}
else
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)) {
//Search for template //Search for template
@@ -72,7 +116,7 @@
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 import(url.pathToFileURL(path.join(directory, "template.mjs")).href)).default Templates[name] = (await import(url.pathToFileURL(path.join(fs.existsSync(path.join(directory, "templates.mjs")) ? directory : path.join(__templates, "classic"), "template.mjs")).href)).default
logger(`metrics/setup > load template [${name}] > success`) logger(`metrics/setup > load template [${name}] > success`)
//Debug //Debug
if (conf.settings.debug) { if (conf.settings.debug) {

View File

@@ -313,4 +313,17 @@
...input ...input
})).toBe(true), 60*1e3) })).toBe(true), 60*1e3)
}) })
) )
describe("Additional options", () => {
test("Community templates", async () => expect(await action.run({
token:"MOCKED_TOKEN",
plugin_pagespeed_token:"MOCKED_TOKEN",
plugin_tweets_token:"MOCKED_TOKEN",
plugin_music_token:"MOCKED_CLIENT_ID, MOCKED_CLIENT_SECRET, MOCKED_REFRESH_TOKEN",
template:"@classic", base:"",
config_timezone:"Europe/Paris",
plugins_errors_fatal:true, dryrun:true, use_mocked_data:true, verify:true,
setup_community_templates:"lowlighter/metrics@master:classic",
})).toBe(true), 60*1e3)
})