feat(app/web): add support for config.presets (#801) [skip ci]
This commit is contained in:
@@ -84,12 +84,16 @@ export default async function metadata({log = true, diff = false} = {}) {
|
|||||||
return {plugins:Plugins, templates:Templates, packaged, descriptor}
|
return {plugins:Plugins, templates:Templates, packaged, descriptor}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**Metadata extractor for inputs */
|
||||||
|
metadata.inputs = {}
|
||||||
|
|
||||||
/**Metadata extractor for templates */
|
/**Metadata extractor for templates */
|
||||||
metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
||||||
try {
|
try {
|
||||||
//Load meta descriptor
|
//Load meta descriptor
|
||||||
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}`
|
const raw = `${await fs.promises.readFile(path.join(__plugins, name, "metadata.yml"), "utf-8")}`
|
||||||
const {inputs, ...meta} = yaml.load(raw)
|
const {inputs, ...meta} = yaml.load(raw)
|
||||||
|
Object.assign(metadata.inputs, inputs)
|
||||||
|
|
||||||
//category
|
//category
|
||||||
if (!categories.includes(meta.category))
|
if (!categories.includes(meta.category))
|
||||||
@@ -345,6 +349,8 @@ metadata.plugin = async function({__plugins, __templates, name, logger}) {
|
|||||||
cell.push(`⏩ Inherits <code>${o.inherits}</code><br>`)
|
cell.push(`⏩ Inherits <code>${o.inherits}</code><br>`)
|
||||||
if (o.global)
|
if (o.global)
|
||||||
cell.push("⏭️ Global option<br>")
|
cell.push("⏭️ Global option<br>")
|
||||||
|
if (/^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(o.preset))
|
||||||
|
cell.push("⏯️ Cannot be preset<br>")
|
||||||
if (o.testing)
|
if (o.testing)
|
||||||
cell.push("🔧 For development<br>")
|
cell.push("🔧 For development<br>")
|
||||||
if (!Object.keys(previous?.inputs ?? {}).includes(option))
|
if (!Object.keys(previous?.inputs ?? {}).includes(option))
|
||||||
|
|||||||
70
source/app/metrics/presets.mjs
Normal file
70
source/app/metrics/presets.mjs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//Imports
|
||||||
|
import fs from "fs/promises"
|
||||||
|
import yaml from "js-yaml"
|
||||||
|
import fetch from "node-fetch"
|
||||||
|
import metadata from "./metadata.mjs"
|
||||||
|
|
||||||
|
/**Presets parser */
|
||||||
|
export default async function presets(list, {log = true, core = null} = {}) {
|
||||||
|
//Init
|
||||||
|
const {plugins} = await metadata({log:false})
|
||||||
|
const {"config.presets":files} = plugins.core.inputs({q:{"config.presets":list}, account:"bypass"})
|
||||||
|
const logger = log ? console.debug : () => null
|
||||||
|
const allowed = Object.entries(metadata.inputs).filter(([_, {type, preset}]) => (type !== "token")&&(!/^(?:[Ff]alse|[Oo]ff|[Nn]o|0)$/.test(preset))).map(([key]) => key)
|
||||||
|
const env = core ? "action" : "web"
|
||||||
|
const options = {}
|
||||||
|
|
||||||
|
//Load presets
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
//Load and parse preset
|
||||||
|
logger(`metrics/presets > loading ${file}`)
|
||||||
|
let text = ""
|
||||||
|
if (file.startsWith("@")) {
|
||||||
|
logger(`metrics/presets > ${file} seems to be predefined preset, fetching`)
|
||||||
|
text = await fetch(`https://raw.githubusercontent.com/lowlighter/metrics/presets/${file.substring(1)}/preset.yaml`).then(response => response.text())
|
||||||
|
}
|
||||||
|
else if (file.startsWith("https://")) {
|
||||||
|
logger(`metrics/presets > ${file} seems to be an url, fetching`)
|
||||||
|
text = await fetch(file).then(response => response.text())
|
||||||
|
}
|
||||||
|
else if (env === "action") {
|
||||||
|
logger(`metrics/presets > ${file} seems to be a local file, reading`)
|
||||||
|
text = `${await fs.readFile(file)}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger(`metrics/presets > ${file} cannot be loaded in current environment ${env}, skipping`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const {schema, with:inputs} = yaml.load(text)
|
||||||
|
logger(`metrics/presets > ${file} preset schema is ${schema}`)
|
||||||
|
|
||||||
|
//Evaluate preset
|
||||||
|
switch (`${schema}`) {
|
||||||
|
case "draft":{
|
||||||
|
for (let [key, value] of Object.entries(inputs)) {
|
||||||
|
if (!allowed.includes(key)) {
|
||||||
|
logger(`metrics/presets > ${key} is specified but is not allowed in preset, skipping`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (env === "web")
|
||||||
|
key = metadata.to.query(key)
|
||||||
|
if (key in options)
|
||||||
|
logger(`metrics/presets > ${key} was already specified by another preset, overwriting`)
|
||||||
|
options[key] = value
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`unsupported preset schema: ${schema}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Handle errors
|
||||||
|
catch (error) {
|
||||||
|
if (env === "action")
|
||||||
|
console.log(`::warning::skipping preset ${file}: ${error.message}`)
|
||||||
|
logger(`metrics/presets > an error occured while loading preset ${file} (${error}), ignoring`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import cache from "memory-cache"
|
|||||||
import util from "util"
|
import util from "util"
|
||||||
import mocks from "../../../tests/mocks/index.mjs"
|
import mocks from "../../../tests/mocks/index.mjs"
|
||||||
import metrics from "../metrics/index.mjs"
|
import metrics from "../metrics/index.mjs"
|
||||||
|
import presets from "../metrics/presets.mjs"
|
||||||
import setup from "../metrics/setup.mjs"
|
import setup from "../metrics/setup.mjs"
|
||||||
|
|
||||||
/**App */
|
/**App */
|
||||||
@@ -252,6 +253,10 @@ export default async function({mock, nosettings} = {}) {
|
|||||||
//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)) {
|
||||||
|
console.debug(`metrics/app/${login} > presets have been specified, loading them`)
|
||||||
|
Object.assign(q, await presets(q["config.presets"]))
|
||||||
|
}
|
||||||
const {rendered, mime} = await metrics({login, q}, {
|
const {rendered, mime} = await metrics({login, q}, {
|
||||||
graphql,
|
graphql,
|
||||||
rest,
|
rest,
|
||||||
|
|||||||
1
source/app/web/settings.example.json
generated
1
source/app/web/settings.example.json
generated
@@ -27,6 +27,7 @@
|
|||||||
},
|
},
|
||||||
"extras": {
|
"extras": {
|
||||||
"default": false, "//": "Default extras state (advised to let 'false' unless in debug mode)",
|
"default": false, "//": "Default extras state (advised to let 'false' unless in debug mode)",
|
||||||
|
"presets": false, "//": "Allow use of 'config.presets' option",
|
||||||
"css": false, "//": "Allow use of 'extras.css' option",
|
"css": false, "//": "Allow use of 'extras.css' option",
|
||||||
"js": false, "//": "Allow use of 'extras.js' option",
|
"js": false, "//": "Allow use of 'extras.js' option",
|
||||||
"features": false, "//": "Enable extra features (advised to let 'false' on web instances)"
|
"features": false, "//": "Enable extra features (advised to let 'false' on web instances)"
|
||||||
|
|||||||
@@ -53,6 +53,38 @@ Content can be manually ordered using `config_order` option.
|
|||||||
|
|
||||||
> 💡 Omitted sections will be appended at the end using default order
|
> 💡 Omitted sections will be appended at the end using default order
|
||||||
|
|
||||||
|
## 🪛 Using presets
|
||||||
|
|
||||||
|
> 🚧 This feature is an early implementation and may change before official release
|
||||||
|
|
||||||
|
It is possible to reuse the same configuration across different repositories and workflows using configuration presets.
|
||||||
|
A preset override the default values of inputs, and multiple presets can be provided at once through URLs or file paths.
|
||||||
|
|
||||||
|
Options resolution is done in the following order:
|
||||||
|
- default values
|
||||||
|
- presets, from first to last
|
||||||
|
- user values
|
||||||
|
|
||||||
|
*Example: using a configuration preset from an url*
|
||||||
|
```yaml
|
||||||
|
- uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
config_presets: https://raw.githubusercontent.com/lowlighter/metrics/presets/lunar-red/preset.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Some presets are hosted on this repository on the [`@presets`](https://github.com/lowlighter/metrics/tree/presets) branch and can be used directly by using using their identifier prefixed by an arobase (`@`).
|
||||||
|
|
||||||
|
*Example: using a pre-defined configuration preset*
|
||||||
|
```yaml
|
||||||
|
- uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
config_presets: "@lunar-red"
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ `🔐 Tokens` and options marked with `⏯️ Cannot be preset`, as they suggest, cannot be preset and thus requires to be explicitely defined to be set.
|
||||||
|
|
||||||
|
> ℹ️ Presets configurations use [schemas](https://github.com/lowlighter/metrics/tree/presets/%40schema) to ensure compatibility between format changes
|
||||||
|
|
||||||
## 🎨 Custom CSS styling
|
## 🎨 Custom CSS styling
|
||||||
|
|
||||||
Additional CSS can be injected using `extras_css` option.
|
Additional CSS can be injected using `extras_css` option.
|
||||||
|
|||||||
@@ -29,6 +29,19 @@
|
|||||||
token: ${{ secrets.METRICS_TOKEN }}
|
token: ${{ secrets.METRICS_TOKEN }}
|
||||||
config_output: png
|
config_output: png
|
||||||
|
|
||||||
|
- name: Presets
|
||||||
|
uses: lowlighter/metrics@latest
|
||||||
|
with:
|
||||||
|
filename: metrics.presets.svg
|
||||||
|
token: ${{ secrets.METRICS_TOKEN }}
|
||||||
|
base: header, repositories
|
||||||
|
config_presets: https://raw.githubusercontent.com/lowlighter/metrics/presets/lunar-red/preset.yaml
|
||||||
|
prod:
|
||||||
|
skip: true
|
||||||
|
test:
|
||||||
|
modes:
|
||||||
|
- web
|
||||||
|
|
||||||
- name: Plugin error example
|
- name: Plugin error example
|
||||||
uses: lowlighter/metrics@latest
|
uses: lowlighter/metrics@latest
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ inputs:
|
|||||||
Defaults to `token` owner username.
|
Defaults to `token` owner username.
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
|
preset: no
|
||||||
|
|
||||||
repo:
|
repo:
|
||||||
description: |
|
description: |
|
||||||
@@ -33,6 +34,7 @@ inputs:
|
|||||||
This option is revevalant only for repositories templates
|
This option is revevalant only for repositories templates
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
|
preset: no
|
||||||
|
|
||||||
committer_token:
|
committer_token:
|
||||||
description: |
|
description: |
|
||||||
@@ -67,6 +69,7 @@ inputs:
|
|||||||
Specify an existing gist id (can be retrieved from its URL) when using `output_action: gist`.
|
Specify an existing gist id (can be retrieved from its URL) when using `output_action: gist`.
|
||||||
type: string
|
type: string
|
||||||
default: ""
|
default: ""
|
||||||
|
preset: no
|
||||||
|
|
||||||
filename:
|
filename:
|
||||||
description: |
|
description: |
|
||||||
@@ -307,6 +310,14 @@ inputs:
|
|||||||
- markdown-pdf
|
- markdown-pdf
|
||||||
- insights
|
- insights
|
||||||
|
|
||||||
|
config_presets:
|
||||||
|
description: Configuration presets
|
||||||
|
type: array
|
||||||
|
format: comma-separated
|
||||||
|
default: ""
|
||||||
|
preset: no
|
||||||
|
example: "@lunar-red"
|
||||||
|
|
||||||
retries:
|
retries:
|
||||||
description: Retries in case of failures (for rendering)
|
description: Retries in case of failures (for rendering)
|
||||||
type: number
|
type: number
|
||||||
@@ -357,6 +368,7 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: yes
|
default: yes
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
plugins_errors_fatal:
|
plugins_errors_fatal:
|
||||||
description: |
|
description: |
|
||||||
@@ -366,6 +378,7 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
description: |
|
description: |
|
||||||
@@ -375,12 +388,14 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
description: SVG validity check
|
description: SVG validity check
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
debug_flags:
|
debug_flags:
|
||||||
description: |
|
description: |
|
||||||
@@ -398,6 +413,7 @@ inputs:
|
|||||||
- --halloween
|
- --halloween
|
||||||
- --error
|
- --error
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
dryrun:
|
dryrun:
|
||||||
description: |
|
description: |
|
||||||
@@ -407,6 +423,7 @@ inputs:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
experimental_features:
|
experimental_features:
|
||||||
description: |
|
description: |
|
||||||
@@ -419,9 +436,11 @@ inputs:
|
|||||||
values:
|
values:
|
||||||
- --optimize-svg
|
- --optimize-svg
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|
||||||
use_mocked_data:
|
use_mocked_data:
|
||||||
description: Use mocked data instead of live APIs
|
description: Use mocked data instead of live APIs
|
||||||
type: boolean
|
type: boolean
|
||||||
default: no
|
default: no
|
||||||
testing: yes
|
testing: yes
|
||||||
|
preset: no
|
||||||
|
|||||||
Reference in New Issue
Block a user