Add templates metadata (#273)
This commit is contained in:
14
.github/quickstart/template/metadata.yml
vendored
Normal file
14
.github/quickstart/template/metadata.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: "🖼️ Template name"
|
||||||
|
extends: classic # Fallback to "classic" template "template.mjs" if not trusted
|
||||||
|
index: ~ # Leave as it (this is used to order plugins on metrics README.md)
|
||||||
|
supports:
|
||||||
|
- user # Support users account
|
||||||
|
- organization # Support organizations account
|
||||||
|
- repository # Support repositories metrics
|
||||||
|
formats:
|
||||||
|
- svg # Support SVG output
|
||||||
|
- png # Support PNG output
|
||||||
|
- jpeg # Support JPEG output
|
||||||
|
- json # Support JSON output
|
||||||
|
- markdown # Support markdown output
|
||||||
|
- markdown-pdf # Support PDF output
|
||||||
@@ -122,6 +122,7 @@ This section explain how metrics is structured.
|
|||||||
* `queries/` contains plugin GraphQL queries
|
* `queries/` contains plugin GraphQL queries
|
||||||
* `source/templates/` contains templates files
|
* `source/templates/` contains templates files
|
||||||
* `README.md` contains template documentation
|
* `README.md` contains template documentation
|
||||||
|
* `metadata.yml` contains template metadata
|
||||||
* `image.svg` contains template image used to render metrics
|
* `image.svg` contains template image used to render metrics
|
||||||
* `style.css` contains style used to render metrics
|
* `style.css` contains style used to render metrics
|
||||||
* `fonts.css` contains additional fonts used to render metrics
|
* `fonts.css` contains additional fonts used to render metrics
|
||||||
@@ -201,6 +202,7 @@ npm run quickstart -- template <template_name>
|
|||||||
It will create a new folder in [`source/templates`](https://github.com/lowlighter/metrics/tree/master/source/templates) with the following files:
|
It will create a new folder in [`source/templates`](https://github.com/lowlighter/metrics/tree/master/source/templates) with the following files:
|
||||||
- A `README.md` to describe your template and document it
|
- A `README.md` to describe your template and document it
|
||||||
- An `image.svg` with base structure for rendering
|
- An `image.svg` with base structure for rendering
|
||||||
|
- A `metadata.yml` which list templates attributes and supported formats
|
||||||
- A `partials/` folder where you'll be able to implement parts of your template
|
- A `partials/` folder where you'll be able to implement parts of your template
|
||||||
- A `partials/_.json` with a JSON array listing these parts in the order you want them displayed (unless overridden by user with `config_order` option)
|
- A `partials/_.json` with a JSON array listing these parts in the order you want them displayed (unless overridden by user with `config_order` option)
|
||||||
|
|
||||||
@@ -209,6 +211,7 @@ If needed, you can also create the following optional files:
|
|||||||
- A `styles.css` with custom CSS that'll style your template
|
- A `styles.css` with custom CSS that'll style your template
|
||||||
- A `template.mjs` with additional data processing and formatting at template-level
|
- A `template.mjs` with additional data processing and formatting at template-level
|
||||||
- When your template is used through `setup_community_templates` on official releases, this is disabled by default unless user trusts it by appending `+trust` at the end of source
|
- When your template is used through `setup_community_templates` on official releases, this is disabled by default unless user trusts it by appending `+trust` at the end of source
|
||||||
|
- You can specify the default `template.mjs` fallback by filling `extends` key in your `metadata.yml` (defaults to `"classic"` template)
|
||||||
|
|
||||||
If inexistent, these will fallback to [`classic`](https://github.com/lowlighter/metrics/tree/master/source/templates/classic) template files.
|
If inexistent, these will fallback to [`classic`](https://github.com/lowlighter/metrics/tree/master/source/templates/classic) template files.
|
||||||
|
|
||||||
@@ -253,6 +256,33 @@ As you can see, we exploit the fact that SVG images are able to render HTML and
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>💬 Filling <code>metadata.yml</code></summary>
|
||||||
|
|
||||||
|
`metadata.yml` is an optional file which describes what account types are allowed, which formats are supported, etc.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
```yaml
|
||||||
|
name: "🖼️ Template name"
|
||||||
|
extends: classic # Fallback to "classic" template "template.mjs" if not trusted
|
||||||
|
index: ~ # Leave as it (this is used to order plugins on metrics README.md)
|
||||||
|
supports:
|
||||||
|
- user # Support users account
|
||||||
|
- organization # Support organizations account
|
||||||
|
- repository # Support repositories metrics
|
||||||
|
formats:
|
||||||
|
- svg # Support SVG output
|
||||||
|
- png # Support PNG output
|
||||||
|
- jpeg # Support JPEG output
|
||||||
|
- json # Support JSON output
|
||||||
|
- markdown # Support markdown output
|
||||||
|
- markdown-pdf # Support PDF output
|
||||||
|
```
|
||||||
|
|
||||||
|
Core plugin will automatically check whether template supports given account or repository and output format and will throw an error in case they aren't compatible.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>💬 Adding custom fonts</summary>
|
<summary>💬 Adding custom fonts</summary>
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
...config
|
...config
|
||||||
} = metadata.plugins.core.inputs.action({core})
|
} = metadata.plugins.core.inputs.action({core})
|
||||||
const q = {...query, ...(_repo ? {repo:_repo} : null), template}
|
const q = {...query, ...(_repo ? {repo:_repo} : null), template}
|
||||||
const _output = ["jpeg", "png", "json", "markdown", "markdown-pdf"].includes(config["config.output"]) ? config["config.output"] : null
|
const _output = ["svg", "jpeg", "png", "json", "markdown", "markdown-pdf"].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"}[_output] ?? _output)
|
const filename = _filename.replace(/[*]/g, {jpeg:"jpg", markdown:"md", "markdown-pdf":"pdf"}[_output] ?? _output)
|
||||||
|
|
||||||
//Docker image
|
//Docker image
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
throw new Error("unsupported template")
|
throw new Error("unsupported template")
|
||||||
const {image, style, fonts, views, partials} = conf.templates[template]
|
const {image, style, fonts, views, partials} = conf.templates[template]
|
||||||
const computer = Templates[template].default || Templates[template]
|
const computer = Templates[template].default || Templates[template]
|
||||||
|
convert = convert ?? conf.metadata.templates[template].formats[0] ?? null
|
||||||
|
console.debug(`metrics/compute/${login} > output format set to ${convert}`)
|
||||||
|
|
||||||
//Initialization
|
//Initialization
|
||||||
const pending = []
|
const pending = []
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
//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}, {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
|
||||||
@@ -150,7 +152,7 @@
|
|||||||
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})
|
const {resized, mime} = await imports.svg.resize(rendered, {paddings:q["config.padding"] || conf.settings.padding, convert:convert === "svg" ? null : convert})
|
||||||
rendered = resized
|
rendered = resized
|
||||||
|
|
||||||
//Result
|
//Result
|
||||||
|
|||||||
@@ -43,8 +43,8 @@
|
|||||||
Templates[name] = await metadata.template({__templates, name, plugins, logger})
|
Templates[name] = await metadata.template({__templates, name, plugins, logger})
|
||||||
}
|
}
|
||||||
//Reorder keys
|
//Reorder keys
|
||||||
const {classic, repository, markdown, community, ...templates} = Templates
|
const {community, ...templates} = Templates
|
||||||
Templates = {classic, repository, ...templates, markdown, community}
|
Templates = {...Object.fromEntries(Object.entries(templates).sort(([_an, a], [_bn, b]) => (a.index ?? Infinity) - (b.index ?? Infinity))), community}
|
||||||
|
|
||||||
//Packaged metadata
|
//Packaged metadata
|
||||||
const packaged = JSON.parse(`${await fs.promises.readFile(__package)}`)
|
const packaged = JSON.parse(`${await fs.promises.readFile(__package)}`)
|
||||||
@@ -254,7 +254,9 @@
|
|||||||
metadata.template = async function({__templates, name, plugins, logger}) {
|
metadata.template = async function({__templates, name, plugins, logger}) {
|
||||||
try {
|
try {
|
||||||
//Load meta descriptor
|
//Load meta descriptor
|
||||||
const raw = `${await fs.promises.readFile(path.join(__templates, name, "README.md"), "utf-8")}`
|
const raw = fs.existsSync(path.join(__templates, name, "metadata.yml")) ? `${await fs.promises.readFile(path.join(__templates, name, "metadata.yml"), "utf-8")}` : ""
|
||||||
|
const readme = `${await fs.promises.readFile(path.join(__templates, name, "README.md"), "utf-8")}`
|
||||||
|
const meta = yaml.load(raw) ?? {}
|
||||||
|
|
||||||
//Compatibility
|
//Compatibility
|
||||||
const partials = path.join(__templates, name, "partials")
|
const partials = path.join(__templates, name, "partials")
|
||||||
@@ -269,11 +271,25 @@
|
|||||||
|
|
||||||
//Result
|
//Result
|
||||||
return {
|
return {
|
||||||
name:raw.match(/^### (?<name>[\s\S]+?)\n/)?.groups?.name?.trim(),
|
name:meta.name ?? readme.match(/^### (?<name>[\s\S]+?)\n/)?.groups?.name?.trim(),
|
||||||
|
index:meta.index ?? null,
|
||||||
|
formats:meta.formats ?? null,
|
||||||
|
supports:meta.supports ?? null,
|
||||||
readme:{
|
readme:{
|
||||||
demo:raw.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? (name === "community" ? '<td align="center" colspan="2">See <a href="/source/templates/community/README.md">documentation</a> 🌍</td>' : "<td></td>"),
|
demo:readme.match(/(?<demo><table>[\s\S]*?<[/]table>)/)?.groups?.demo?.replace(/<[/]?(?:table|tr)>/g, "")?.trim() ?? (name === "community" ? '<td align="center" colspan="2">See <a href="/source/templates/community/README.md">documentation</a> 🌍</td>' : "<td></td>"),
|
||||||
compatibility:{...compatibility, base:true},
|
compatibility:{...compatibility, base:true},
|
||||||
},
|
},
|
||||||
|
check({q, account = "bypass", format = null}) {
|
||||||
|
//Support check
|
||||||
|
if (account !== "bypass") {
|
||||||
|
const context = q.repo ? "repository" : account
|
||||||
|
if ((Array.isArray(this.supports))&&(!this.supports.includes(context)))
|
||||||
|
throw new Error(`not supported for: ${context}`)
|
||||||
|
}
|
||||||
|
//Format check
|
||||||
|
if ((format)&&(Array.isArray(this.formats))&&(!this.formats.includes(format)))
|
||||||
|
throw new Error(`not supported for: ${format}`)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import processes from "child_process"
|
import processes from "child_process"
|
||||||
import util from "util"
|
import util from "util"
|
||||||
import url from "url"
|
import url from "url"
|
||||||
|
import yaml from "js-yaml"
|
||||||
import OctokitRest from "@octokit/rest"
|
import OctokitRest from "@octokit/rest"
|
||||||
|
|
||||||
//Templates and plugins
|
//Templates and plugins
|
||||||
@@ -94,6 +95,16 @@
|
|||||||
else if (fs.existsSync(path.join(__templates, `@${name}`, "template.mjs"))) {
|
else if (fs.existsSync(path.join(__templates, `@${name}`, "template.mjs"))) {
|
||||||
logger(`metrics/setup > removing @${name}/template.mjs`)
|
logger(`metrics/setup > removing @${name}/template.mjs`)
|
||||||
await fs.promises.unlink(path.join(__templates, `@${name}`, "template.mjs"))
|
await fs.promises.unlink(path.join(__templates, `@${name}`, "template.mjs"))
|
||||||
|
const inherit = yaml.load(`${fs.promises.readFile(path.join(__templates, `@${name}`, "metadata.yml"))}`).extends ?? null
|
||||||
|
if (inherit) {
|
||||||
|
logger(`metrics/setup > @${name} extends from ${inherit}`)
|
||||||
|
if (fs.existsSync(path.join(__templates, inherit, "template.mjs"))) {
|
||||||
|
logger(`metrics/setup > @${name} extended from ${inherit}`)
|
||||||
|
await fs.promises.copyFile(path.join(__templates, inherit, "template.mjs"), path.join(__templates, `@${name}`, "template.mjs"))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
logger(`metrics/setup > @${name} could not extends ${inherit} as it does not exist`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
logger(`metrics/setup > @${name}/template.mjs does not exist`)
|
logger(`metrics/setup > @${name}/template.mjs does not exist`)
|
||||||
@@ -194,7 +205,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load metadata (plugins)
|
//Load metadata
|
||||||
conf.metadata = await metadata({log})
|
conf.metadata = await metadata({log})
|
||||||
|
|
||||||
//Store authenticated user
|
//Store authenticated user
|
||||||
|
|||||||
@@ -261,7 +261,7 @@
|
|||||||
graphql, rest, plugins, conf,
|
graphql, rest, plugins, conf,
|
||||||
die:q["plugins.errors.fatal"] ?? false,
|
die:q["plugins.errors.fatal"] ?? false,
|
||||||
verify:q.verify ?? false,
|
verify:q.verify ?? false,
|
||||||
convert:["jpeg", "png", "json", "markdown", "markdown-pdf"].includes(q["config.output"]) ? q["config.output"] : null,
|
convert:["svg", "jpeg", "png", "json", "markdown", "markdown-pdf"].includes(q["config.output"]) ? q["config.output"] : null,
|
||||||
}, {Plugins, Templates})
|
}, {Plugins, Templates})
|
||||||
//Cache
|
//Cache
|
||||||
if ((!debug)&&(cached)) {
|
if ((!debug)&&(cached)) {
|
||||||
@@ -284,6 +284,11 @@
|
|||||||
console.debug(`metrics/app/${login} > 400 (bad request)`)
|
console.debug(`metrics/app/${login} > 400 (bad request)`)
|
||||||
return res.status(400).send("Bad request: unsupported template")
|
return res.status(400).send("Bad request: unsupported template")
|
||||||
}
|
}
|
||||||
|
//Unsupported output format or account type
|
||||||
|
if ((error instanceof Error)&&(/^not supported for: [\s\S]*$/.test(error.message))) {
|
||||||
|
console.debug(`metrics/app/${login} > 406 (Not Acceptable)`)
|
||||||
|
return res.status(406).send("Not Acceptable: unsupported output format or account type for specified parameters")
|
||||||
|
}
|
||||||
//GitHub failed request
|
//GitHub failed request
|
||||||
if ((error instanceof Error)&&(/this may be the result of a timeout, or it could be a GitHub bug/i.test(error.errors?.[0]?.message))) {
|
if ((error instanceof Error)&&(/this may be the result of a timeout, or it could be a GitHub bug/i.test(error.errors?.[0]?.message))) {
|
||||||
console.debug(`metrics/app/${login} > 502 (bad gateway from GitHub)`)
|
console.debug(`metrics/app/${login} > 502 (bad gateway from GitHub)`)
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//Setup
|
//Setup
|
||||||
export default async function({login, q}, {conf, data, rest, graphql, plugins, queries, account}, {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.timezone":_timezone, "debug.flags":dflags} = imports.metadata.plugins.core.inputs({data, account, q})
|
const {"config.animations":animations, "config.timezone":_timezone, "debug.flags":dflags} = imports.metadata.plugins.core.inputs({data, account, q})
|
||||||
|
imports.metadata.templates[template].check({q, account, format:convert})
|
||||||
|
|
||||||
//Init
|
//Init
|
||||||
const computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, 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}}
|
const computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, 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}}
|
||||||
|
|||||||
@@ -186,8 +186,9 @@ inputs:
|
|||||||
config_output:
|
config_output:
|
||||||
description: Output image format
|
description: Output image format
|
||||||
type: string
|
type: string
|
||||||
default: svg
|
default: auto
|
||||||
values:
|
values:
|
||||||
|
- auto # Defaults to template default
|
||||||
- svg
|
- svg
|
||||||
- png # Does not support animations
|
- png # Does not support animations
|
||||||
- jpeg # Does not support animations and transparency
|
- jpeg # Does not support animations and transparency
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
### 📗 Classic
|
### 📗 Classic template
|
||||||
|
|
||||||
Default template, mimicking GitHub visual identity.
|
Default template, mimicking GitHub visual identity.
|
||||||
|
|
||||||
@@ -11,6 +11,8 @@ Default template, mimicking GitHub visual identity.
|
|||||||
|
|
||||||
#### ℹ️ Examples workflows
|
#### ℹ️ Examples workflows
|
||||||
|
|
||||||
|
[➡️ Supported formats and inputs](metadata.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: lowlighter/metrics@latest
|
- uses: lowlighter/metrics@latest
|
||||||
with:
|
with:
|
||||||
|
|||||||
10
source/templates/classic/metadata.yml
Normal file
10
source/templates/classic/metadata.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name: "📗 Classic template"
|
||||||
|
index: 0
|
||||||
|
supports:
|
||||||
|
- user
|
||||||
|
- organization
|
||||||
|
formats:
|
||||||
|
- svg
|
||||||
|
- png
|
||||||
|
- jpeg
|
||||||
|
- json
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
### 📒 Markdown
|
### 📒 Markdown template
|
||||||
|
|
||||||
Markdown template can render a **markdown template** by interpreting **templating brackets** `{{` and `}}`.
|
Markdown template can render a **markdown template** by interpreting **templating brackets** `{{` and `}}`.
|
||||||
|
|
||||||
@@ -24,6 +24,8 @@ For convenience, several useful properties are aliased in [/source/templates/mar
|
|||||||
|
|
||||||
#### ℹ️ Examples workflows
|
#### ℹ️ Examples workflows
|
||||||
|
|
||||||
|
[➡️ Supported formats and inputs](metadata.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Markdown output
|
# Markdown output
|
||||||
- uses: lowlighter/metrics@latest
|
- uses: lowlighter/metrics@latest
|
||||||
@@ -33,7 +35,6 @@ For convenience, several useful properties are aliased in [/source/templates/mar
|
|||||||
filename: README.md # Output file
|
filename: README.md # Output file
|
||||||
markdown: TEMPLATE.md # Template file
|
markdown: TEMPLATE.md # Template file
|
||||||
markdown_cache: .cache # Cache folder
|
markdown_cache: .cache # Cache folder
|
||||||
config_output: markdown # Output as markdown file
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|||||||
9
source/templates/markdown/metadata.yml
Normal file
9
source/templates/markdown/metadata.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: "📒 Markdown template"
|
||||||
|
index: 3
|
||||||
|
supports:
|
||||||
|
- user
|
||||||
|
- organization
|
||||||
|
formats:
|
||||||
|
- markdown
|
||||||
|
- markdown-pdf
|
||||||
|
- json
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
### 📘 Repository
|
### 📘 Repository template
|
||||||
|
|
||||||
Template crafted for repositories, mimicking GitHub visual identity.
|
Template crafted for repositories, mimicking GitHub visual identity.
|
||||||
|
|
||||||
@@ -11,6 +11,8 @@ Template crafted for repositories, mimicking GitHub visual identity.
|
|||||||
|
|
||||||
#### ℹ️ Examples workflows
|
#### ℹ️ Examples workflows
|
||||||
|
|
||||||
|
[➡️ Supported formats and inputs](metadata.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: lowlighter/metrics@latest
|
- uses: lowlighter/metrics@latest
|
||||||
with:
|
with:
|
||||||
|
|||||||
9
source/templates/repository/metadata.yml
Normal file
9
source/templates/repository/metadata.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: "📘 Repository template"
|
||||||
|
index: 1
|
||||||
|
supports:
|
||||||
|
- repository
|
||||||
|
formats:
|
||||||
|
- svg
|
||||||
|
- png
|
||||||
|
- jpeg
|
||||||
|
- json
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
### 📙 Terminal
|
### 📙 Terminal template
|
||||||
|
|
||||||
Terminal template, mimicking a SSH session.
|
Terminal template, mimicking a SSH session.
|
||||||
|
|
||||||
@@ -11,6 +11,8 @@ Terminal template, mimicking a SSH session.
|
|||||||
|
|
||||||
#### ℹ️ Examples workflows
|
#### ℹ️ Examples workflows
|
||||||
|
|
||||||
|
[➡️ Supported formats and inputs](metadata.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: lowlighter/metrics@latest
|
- uses: lowlighter/metrics@latest
|
||||||
with:
|
with:
|
||||||
|
|||||||
10
source/templates/terminal/metadata.yml
Normal file
10
source/templates/terminal/metadata.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name: "📙 Terminal template"
|
||||||
|
index: 2
|
||||||
|
supports:
|
||||||
|
- user
|
||||||
|
- organization
|
||||||
|
formats:
|
||||||
|
- svg
|
||||||
|
- png
|
||||||
|
- jpeg
|
||||||
|
- json
|
||||||
Reference in New Issue
Block a user