Version 2.10 (#30)

This commit is contained in:
Simon Lecoq authored and GitHub committed 2020-12-28 20:04:44 +01:00
1 parent 8135bf19a6
commit 159b0757a8
39 files changed
+922 -559

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

+25 -91
View File
@@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
template: ["classic","terminal"]
template: ["classic","repository","terminal"]
steps:
- name: ${{ matrix.template }} > Base
@@ -53,7 +53,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: header, activity, community, repositories, metadata
plugins_errors_fatal: yes
@@ -63,32 +63,21 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_pagespeed: yes
plugin_pagespeed_token: ${{ secrets.PAGESPEED_TOKEN }}
- name: ${{ matrix.template }} > Plugin > PageSpeed (detailed)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_pagespeed: yes
plugin_pagespeed_detailed: yes
plugin_pagespeed_token: ${{ secrets.PAGESPEED_TOKEN }}
plugin_pagespeed_screenshot: yes
- name: ${{ matrix.template }} > Plugin > Music (playlist - apple)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
@@ -100,7 +89,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
@@ -112,7 +101,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
@@ -125,7 +114,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
@@ -137,18 +126,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_isocalendar: yes
- name: ${{ matrix.template }} > Plugin > Isocalendar (full year)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
@@ -160,25 +138,12 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_habits: yes
plugin_habits_from: 5
- name: ${{ matrix.template }} > Plugin > Habits (charts)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_habits: yes
plugin_habits_from: 5
plugin_habits_facts: no
plugin_habits_charts: yes
- name: ${{ matrix.template }} > Plugin > Languages
@@ -186,7 +151,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
@@ -197,32 +162,22 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_followup: yes
- name: ${{ matrix.template }} > Plugin > Lines
- name: ${{ matrix.template }} > Plugin > Lines and Traffic
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_lines: yes
- name: ${{ matrix.template }} > Plugin > Traffic
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_traffic: yes
- name: ${{ matrix.template }} > Plugin > Gists
@@ -230,70 +185,49 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_gists: yes
- name: ${{ matrix.template }} > Plugin > Topics (stars)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_topics: yes
plugin_topics_sort: stars
- name: ${{ matrix.template }} > Plugin > Topics (activity)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_topics: yes
plugin_topics_sort: activity
- name: ${{ matrix.template }} > Plugin > Topics (starred)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_topics: yes
plugin_topics_sort: starred
plugin_topics_mode: starred
plugin_topics_sort: random
- name: ${{ matrix.template }} > Plugin > Topics (random)
- name: ${{ matrix.template }} > Plugin > Topics (mastered)
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_topics: yes
plugin_topics_sort: random
plugin_topics_mode: mastered
plugin_topics_sort: stars
- name: ${{ matrix.template }} > Plugin > Projects
uses: lowlighter/metrics@master
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
plugin_projects: yes
plugin_projects_repositories: lowlighter/metrics/projects/1
plugin_projects_limit: 2
- name: ${{ matrix.template }} > Plugin > Tweets
@@ -301,7 +235,7 @@ jobs:
with:
token: ${{ secrets.METRICS_TOKEN }}
dryrun: yes
repositories: 1
repositories: 0
template: ${{ matrix.template }}
base: ""
plugins_errors_fatal: yes
+63 -48
View File
@@ -6,69 +6,98 @@ Nice ! Read the few sections below to understand how this project is structured.
### 👨‍💻 General informations
#### Adding new metrics and plugins through GraphQL API, REST API or Third-Party service
#### Adding new metrics through GraphQL API, REST API or Third-Party service
When updating a single template, changes should be made through [GitHub GraphQL API](https://docs.github.com/en/graphql) by editing the GraphQL query from `templates/*/query.graphql`. Computed or formatting can be made in `templates/*/template.mjs`.
Raw queried data should be exposed directly into `data.user`, whereas computed data should be stored in `data.computed`.
When possible, try to use the [GitHub GraphQL API](https://docs.github.com/en/graphql) by editing queries in `source/queries` or the [GitHub Rest API](https://docs.github.com/en/rest). Use `puppeteer` in last resort.
When making a new plugin, a new folder with its name must be created in `src/plugins`.
Plugins entry point `src/plugins/*/index.mjs` will have access to `{login, q, imports, data, computed, rest, graphql}`.
It should be self-sufficient and independant from others plugins and used template.
Data generated should be exposed in `data.computed.plugins[plugin]` where `plugin` is the plugin's name.
Data computing and formatting should be made in `templates/*/template.mjs` if it's template specific, or in `templates/common.mjs` if it can be made available for all templates.
Raw queried data should be be exposed directly into `data.user`, whereas computed data should be stored in `data.computed`.
#### Updating SVG templates
The SVG templates are located in `templates/*/image.svg` and include CSS from `templates/*/style.css`.
SVG templates are located in `templates/*/image.svg` and include CSS from `templates/*/style.css`.
It is rendered with [EJS](https://github.com/mde/ejs) so it is actually possible to include variables (e.g. `<%= user.name %>`) and execute simple code, like control statements.
These are rendered through [EJS](https://github.com/mde/ejs), so it is actually possible to include variables (e.g. `<%= user.name %>`) and execute simple code, like control statements.
Plugins errors should be handled gracefully by displaying the error message.
Exposed variables contains `user`, `computed` and `plugins` data, along with custom `style` and `fonts`.
#### Metrics server and GitHub action
#### Creating a new plugin
Unless when integrating new features directly tied to these, it is not needed to edit them when creating a new template or plugin.
Keep in mind that SVG image is actually generated from `src/metrics.mjs`, independently from the metrics server and GitHub action.
Start by creating a new folder in `source/plugins`, along with its entry point `index.mjs`.
Metrics server code is located in `src/app.mjs` and instantiates an `express` server app, `octokit`s instances, middlewares (like rate-limiter) and routes.
A plugin function has access to a lot of data, such as user's `login`, `rest` and `graphql` GitHub API handlers, `imports` of various utilitaries functions and modules, and various data. See others plugins for examples.
GitHub action code is located in `action/index.mjs` and instantiates `octokit`s instances and retrieves action parameters.
It then use directly `src/metrics.mjs` to generate the SVG image and commit them to user's repository.
Plugins should be self-sufficient and independant from others plugins.
Plugins errors should be handled gracefully by displaying an error message when it fails.
For user's convenience, a placeholder image can be generated to preview metrics without executing queries.
This use a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) which "fills" with dummy data, though it has its limit (especially with iterable structures) so you may need to patch the placeholder function. It is located in `source/metrics.mjs`
When adding new options, be sure to patch both `action.yml` and `action/index.mjs` to support them.
#### Metrics web instance
Web instance code is located in `source/app.mjs` and instantiates an `express` server app.
#### GitHub action
GitHub action code is located in `action/index.mjs` and retrieves action parameters, which are converted into `q` parameters.
Once SVG image is generated through `source/metrics.mjs`, it is committed to user's repository.
#### Testing new features
To test new features, setup a metrics server with a test token and `debug` mode enabled.
It allows fast prototyping and to rapidly test SVG renders in a browser.
To test new features, setup a metrics web instance with a personal token and `debug` mode enabled.
You can then test SVG renders in your browser and ensure that everything works as expected.
### 🗛 Fonts
Follow the following process to integrate custom fonts.
It should be avoided when possible as it increases drastically the size of generated metrics.
1. Find a font on [fonts.google.com](https://fonts.google.com/)
- Select regular, bold, italic and bold+italic fonts
- Open `embed` tab and extract the `href`
2. Open extracted `href` and append `&text=` params with used characters from SVG
- e.g. `&text=%26%27"%7C%60%5E%40°%3F!%23%24%25()*%2B%2C-.%2F0123456789%3A%3B<%3D>ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5D_abcdefghijklmnopqrstuvwxyz%7B%7D~─└├▇□✕`
3. Download each font file from url links from the generated stylesheet
4. Convert them into base64 with `woff` extension on [transfonter.org]https://transfonter.org/) and download archive
5. Extract archive and copy the content of the generated stylesheet to `templates/*/fonts.css`
6. Update your template
- Include `<defs><style><%= fonts %></style></defs>` to your `templates/*/image.svg`
- Edit your `templates/*/style.css` to use yout new font
### 🗂️ Project structure
#### Metrics generator
* `src/setup.mjs` contains the configuration setup
* `src/metrics.mjs` contains the metrics renderer
* `src/templates/*` contains templates files
* `src/templates/*/image.svg` contains the template used by the generated SVG image
* `src/templates/*/query.graphql` is the GraphQL query sent to GitHub GraphQL API
* `src/templates/*/style.css` contains the style used by the generated SVG image
* `src/templates/*/template.mjs` contains the code which prepares data for rendering
* `src/plugins/*` contains the source code of metrics plugins
* `source/setup.mjs` contains configuration setup
* `source/metrics.mjs` contains the metrics renderer
* `source/templates/*` contains templates files
* `source/templates/*/image.svg` contains the image template used to render metrics
* `source/templates/*/style.css` contains the style used to render metrics
* `source/templates/*/fonts.css` contains additional fonts used to render metrics
* `source/templates/*/template.mjs` contains the code used to prepare metrics data before rendering
* `source/plugins/*` contains source code of plugins
* `source/queries/*` contains GraphQL queries
#### Metrics server instance
#### Web instance
* `index.mjs` contains the metrics server entry point
* `src/app.mjs` contains the metrics server code which serves, renders, restricts/rate limit, etc.
* `src/html/*` contains the metrics server static files
* `index.mjs` contains metrics web instance entry point
* `source/app.mjs` contains metrics web instance source code
* `source/html/*` contains metrics web instance static files
#### GitHub action
* `action.yml` contains the GitHub action descriptor
* `action/index.mjs` contains the GitHub action code
* `action.yml` contains GitHub action descriptor
* `action/index.mjs` contains GitHub action source code
* `action/dist/index.js` contains compiled the GitHub action code (auto-generated)
#### Others
* `tests/metrics.mjs` contains tests
* `utils/build.mjs` allows to rebuild plugins and template indexes and GitHub action
* `utils/build.mjs` contains a tool used to rebuild plugins and template indexes and GitHub action
### 📦 Used packages
@@ -100,17 +129,3 @@ It allows fast prototyping and to rapidly test SVG renders in a browser.
* To print colors in console
* [babel/minify](https://github.com/babel/minify)
* To minify code
### 🗛 Fonts
1. Find a font on [fonts.google.com](https://fonts.google.com/)
- Select regular, bold, italic and bold+italic fonts
- Open `embed` tab and extract the `href`
2. Open extracted `href` and append `&text=` params with used characters from SVG
- e.g. `&text=%26%27"%7C%60%5E%40°%3F!%23%24%25()*%2B%2C-.%2F0123456789%3A%3B<%3D>ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5D_abcdefghijklmnopqrstuvwxyz%7B%7D~─└├▇□✕`
3. Download each font file from url links from the generated stylesheet
4. Convert them into base64 with `woff` extension on [transfonter.org]https://transfonter.org/) and download archive
5. Extract archive and copy the content of the generated stylesheet to `templates/*/fonts.css`
6. Update your template
- Include `<defs><style><%= fonts %></style></defs>` to your `templates/*/image.svg`
- Edit your `templates/*/style.css` to use yout new font
+93 -19
View File
@@ -18,21 +18,26 @@ But there's more with [plugins](https://github.com/lowlighter/metrics/tree/maste
<a href="https://github.com/lowlighter/metrics#%EF%B8%8F-pagespeed">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.pagespeed.svg" alt="" width="400">
</a>
<details><summary>Alternate version</summary>
<details><summary>Detailed audit version</summary>
<a href="https://github.com/lowlighter/metrics#%EF%B8%8F-pagespeed">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.pagespeed.detailed.svg" alt="" width="400">
</a>
</detail>
</details>
<details><summary>With screenshot version</summary>
<a href="https://github.com/lowlighter/metrics#%EF%B8%8F-pagespeed">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.pagespeed.screenshot.svg" alt="" width="400">
</a>
</details>
</td>
<td>
<a href="https://github.com/lowlighter/metrics#-isometric-calendar">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.isocalendar.svg" alt="" width="400">
</a>
<details><summary>Alternate version</summary>
<details><summary>Full year version</summary>
<a href="https://github.com/lowlighter/metrics#-isometric-calendar">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.isocalendar.fullyear.svg" alt="" width="400">
</a>
</detail>
</details>
</td>
</tr>
<tr>
@@ -68,17 +73,22 @@ But there's more with [plugins](https://github.com/lowlighter/metrics/tree/maste
</td>
</tr>
<tr>
<th><a href="https://github.com/lowlighter/metrics#-lines">📌 Starred topics plugin</a></th>
<th><a href="https://github.com/lowlighter/metrics#-traffic">🗂️ Active projects plugin</a></th>
<th><a href="https://github.com/lowlighter/metrics#-topics">📌 Starred topics plugin</a></th>
<th><a href="https://github.com/lowlighter/metrics#%EF%B8%8F-projects">🗂️ Active projects plugin</a></th>
</tr>
<tr>
<td>
<a href="https://github.com/lowlighter/metrics#-lines">
<a href="https://github.com/lowlighter/metrics#-topics">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.topics.svg" alt="" width="400">
</a>
<details open><summary>Mastered and known technologies version</summary>
<a href="https://github.com/lowlighter/metrics#-topics">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.topics.mastered.svg" alt="" width="400">
</a>
</details>
</td>
<td>
<a href="https://github.com/lowlighter/metrics#-traffic">
<a href="https://github.com/lowlighter/metrics#%EF%B8%8F-projects">
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.projects.svg" alt="" width="400">
</a>
</td>
@@ -489,23 +499,23 @@ Used template defaults to the `classic` one.
<tr>
<th>Classic</th>
<td>✔️</td>
<td><span title="'plugin_pagespeed_screenshot' is available on @master">✔️<sup>N</sup></span></td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td><span title="'plugin_topics_mode' is available on @master">✔️<sup>N</sup></span></td>
<td><span title="'plugin_projects_repositories' is available on @master">✔️<sup>N</sup></span></td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td><span title="'Files' and 'Comments' are available on @master">✔️<sup>N</sup></span></td>
</tr>
<tr>
<th>Terminal</th>
<td><span title="'Available for hire' and 'Cake day' are currently not displayed">✔️<sup>P</sup></span></td>
<td><span title="'Available for hire' and 'Cake day' are not displayed">✔️<sup>P</sup></span></td>
<td>✔️</td>
<td>❌</td>
<td>❌</td>
@@ -520,12 +530,30 @@ Used template defaults to the `classic` one.
<td>❌</td>
<td>✔️</td>
</tr>
<tr>
<th>Repository<sup>MR</sup></th>
<td>✔️</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>✔️</td>
<td>❌</td>
<td>✔️</td>
<td>✔️</td>
<td>✔️</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
</table>
**Legend**
* **P** : Partial support *(Hover cell for more informations)*
* **M** : Plugin is not released yet but is available on `@master`
* **N** : Plugin is already released, but new features are available on `@master`
* **M** : Feature is not released yet but is 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)
## 🧩 Plugins
@@ -600,6 +628,20 @@ Add the following to your workflow instead :
plugin_pagespeed_token: ${{ secrets.PAGESPEED_TOKEN }}
```
🚧 The feature below is only available on @master
You can also display the screenshot taken by PageSpeed API :
![Pagespeed plugin (screenshot)](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.pagespeed.screenshot.svg)
Add the following to your workflow :
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_pagespeed_screenshot: yes
```
</details>
### 📅 Isometric calendar
@@ -872,6 +914,21 @@ Add the following to your workflow :
plugin_topics_limit: 15
```
🚧 The feature below is only available on @master
It is possible to display starred topics as `Mastered and known technologies` instead :
![Topics plugin (mastered)](https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.topics.mastered.svg)
Add the following to your workflow instead :
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_topics: yes
plugin_topics_mode: mastered
```
</details>
### 🗂️ Projects
@@ -914,6 +971,23 @@ Fill the informations and set visibility to *public* :
</details>
🚧 The feature below is only available on @master
It is possible to display projects related to repositories along with personal projects.
To do so, open your repository project and retrieve the last url endpoint, in the format `:user/:repository/projects/:project_id` (for example, `lowlighter/metrics/projects/1`) and add it in the `plugin_projects_repositories` option. Enable `Track project progress` in the project settings to display a progress bar in generated metrics.
![Add a repository project](.github/readme/imgs/plugin_projects_repositories.png)
Add the following to your workflow :
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_projects: yes
plugin_projects_repositories: :user/:repository1/projects/:project_id, :user/:repository2/projects/:project_id, ...
```
</details>
### 👨‍💻 Lines
+31 -2
View File
@@ -59,6 +59,13 @@ inputs:
description: Template to use
default: classic
# Raw query parameters (JSON string)
# Some templates may require additional parameters which you can specify here
# Do not use this option to pass plugins parameters as they'll be overwritten by the other options
query:
description: Additional query parameters
default: "{}"
# Template base content
# Pass a string of comma-separated values
# To disable everything (like if you want to use a plugin as standalone), pass an empty string
@@ -86,6 +93,11 @@ inputs:
description: Display additional PageSpeed metrics
default: no
# Display the final screenshot of audited website taken by PageSpeed audit
plugin_pagespeed_screenshot:
description: Display a screenshot of your website
default: no
# PageSpeed API token (optional, avoid hitting requests limit)
# See https://developers.google.com/speed/docs/insights/v5/get-started for more informations
plugin_pagespeed_token:
@@ -243,6 +255,15 @@ inputs:
description: Display starred topics
default: no
# Topics plugin mode
# Change the way topics are displayed
# Supported values are :
# - "starred" to display starred topics as interests labels
# - "mastered" to display starred topics as mastered/known technologies icons
plugin_topics_mode:
description: Display starred topics
default: "starred"
# Sorting method of displayed topics
# Supported values are :
# - "stars" to sort them from most starred to least starred
@@ -253,11 +274,11 @@ inputs:
description: Sorting method of starred topics
default: "stars"
# Number of topics to display (between 1 and 20)
# Number of topics to display (between 0 and 20)
# If more topics must be displayed, they will be grouped in an ellipsis
plugin_topics_limit:
description: Number of starred topics to display
default: 15
default: 0
# Projects plugin
# Display active projects
@@ -265,6 +286,14 @@ inputs:
description: Display active projects
default: no
# Display active repository projects
# By default, only user owned projects are displayed, with this option it is possible to display projects from repositories
# List of comma-separated projects identifier, in the following format :user/:repo/projects/:project_id
# The limit of displayed projects will automatically be updated so listed projects will fit
plugin_projects_repositories:
description: List of repository project identifiers to disaplay
default: ""
# Number of active projects to display
# Between 1 and 100
plugin_projects_limit:
+9 -61
View File
@@ -1,75 +1,23 @@
module.exports=(()=>{var _Mathhypot=Math.hypot,_Mathacos=Math.acos,_Mathtan=Math.tan,_Mathasin=Math.asin,_Mathsin=Math.sin,_Mathcos=Math.cos,_MathPI=Math.PI,_Mathsqrt=Math.sqrt,_NumberisInteger=Number.isInteger,_NumberPOSITIVE_INFINITY=Number.POSITIVE_INFINITY,_NumberNEGATIVE_INFINITY=Number.NEGATIVE_INFINITY,_Stringprototype=String.prototype,_NumberMAX_SAFE_INTEGER=Number.MAX_SAFE_INTEGER,_StringfromCharCode=String.fromCharCode,_Mathabs=Math.abs,_Mathpow=Math.pow,_Mathround=Math.round,_Mathfloor=Math.floor,_Mathceil=Math.ceil,_Mathmax=Math.max,_Mathmin=Math.min;function __webpack_require__(e){if(__webpack_module_cache__[e])return __webpack_module_cache__[e].exports;var t=__webpack_module_cache__[e]={exports:{}},r=!0;try{__webpack_modules__[e].call(t.exports,t,t.exports,__webpack_require__),r=!1}finally{r&&delete __webpack_module_cache__[e]}return t.exports}var __webpack_modules__={45274:(e,t,r)=>{"use strict";async function n({log:e=!0}={}){const t=e?console.debug:()=>null;t(`metrics/setup > setup`);const n="src/templates",a={templates:{},settings:{},statics:r.ab+"html",node_modules:b.resolve("node_modules")};if(t(`metrics/setup > load settings.json`),y.existsSync(r.ab+"settings.json")?(a.settings=JSON.parse(`${await y.promises.readFile(b.resolve("settings.json"))}`),t(`metrics/setup > load settings.json > success`)):t(`metrics/setup > load settings.json > (missing)`),a.settings.templates||(a.settings.templates={default:"classic",enabled:[]}),a.settings.plugins||(a.settings.plugins={}),a.settings.plugins.base={parts:["header","activity","community","repositories","metadata"]},a.settings.debug&&t(S.inspect(a.settings,{depth:1/0,maxStringLength:256})),t(`metrics/setup > load package.json`),y.existsSync(r.ab+"package.json")?(a.package=JSON.parse(`${await y.promises.readFile(b.resolve("package.json"))}`),t(`metrics/setup > load package.json > success`)):(t(`metrics/setup > load package.json > (missing)`),a.package={version:"2.9.0",author:"lowlighter"}),y.existsSync(r.ab+"templates"))for(const e of await y.promises.readdir(n)){if(/.*[.]mjs$/.test(e))continue;t(`metrics/setup > load template [${e}]`);const r=[`${n}/${e}/query.graphql`,`${n}/${e}/image.svg`,`${n}/${e}/style.css`,`${n}/${e}/fonts.css`],[o,i,s,l]=await Promise.all(r.map(async e=>`${await y.promises.readFile(b.resolve(e))}`));a.templates[e]={query:o,image:i,style:s,fonts:l},t(`metrics/setup > load template [${e}] > success`),a.settings.debug&&Object.defineProperty(a.templates,e,{get(){t(`metrics/setup > reload template [${e}]`);const[n,a,o,i]=r.map(e=>`${y.readFileSync(b.resolve(e))}`);return t(`metrics/setup > reload template [${e}] > success`),{query:n,image:a,style:o,fonts:i}}})}else t(`metrics/setup > load templates from build`),a.templates=JSON.parse(Buffer.from(`eyJjbGFzc2ljIjp7InF1ZXJ5IjoicXVlcnkgTWV0cmljcyB7XHJcbiAgdXNlcihsb2dpbjogJGxvZ2luKSB7XHJcbiAgICBkYXRhYmFzZUlkXHJcbiAgICBuYW1lXHJcbiAgICBsb2dpblxyXG4gICAgY3JlYXRlZEF0XHJcbiAgICBhdmF0YXJVcmxcclxuICAgIHdlYnNpdGVVcmxcclxuICAgIGlzSGlyZWFibGVcclxuICAgIHR3aXR0ZXJVc2VybmFtZVxyXG4gICAgZ2lzdHMge1xyXG4gICAgICB0b3RhbENvdW50XHJcbiAgICB9XHJcbiAgICByZXBvc2l0b3JpZXMobGFzdDogJHJlcG9zaXRvcmllcywgaXNGb3JrOiBmYWxzZSwgb3duZXJBZmZpbGlhdGlvbnM6IE9XTkVSKSB7XHJcbiAgICAgIHRvdGFsQ291bnRcclxuICAgICAgdG90YWxEaXNrVXNhZ2VcclxuICAgICAgbm9kZXMge1xyXG4gICAgICAgIG5hbWVcclxuICAgICAgICB3YXRjaGVycyB7XHJcbiAgICAgICAgICB0b3RhbENvdW50XHJcbiAgICAgICAgfVxyXG4gICAgICAgIHN0YXJnYXplcnMge1xyXG4gICAgICAgICAgdG90YWxDb3VudFxyXG4gICAgICAgIH1cclxuICAgICAgICBsYW5ndWFnZXMoZmlyc3Q6IDQpIHtcclxuICAgICAgICAgIGVkZ2VzIHtcclxuICAgICAgICAgICAgc2l6ZVxyXG4gICAgICAgICAgICBub2RlIHtcclxuICAgICAgICAgICAgICBjb2xvclxyXG4gICAgICAgICAgICAgIG5hbWVcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgICAgICBpc3N1ZXNfb3BlbjogaXNzdWVzKHN0YXRlczogT1BFTikge1xyXG4gICAgICAgICAgdG90YWxDb3VudFxyXG4gICAgICAgIH1cclxuICAgICAgICBpc3N1ZXNfY2xvc2VkOiBpc3N1ZXMoc3RhdGVzOiBDTE9TRUQpIHtcclxuICAgICAgICAgIHRvdGFsQ291bnRcclxuICAgICAgICB9XHJcbiAgICAgICAgcHJfb3BlbjogcHVsbFJlcXVlc3RzKHN0YXRlczogT1BFTikge1xyXG4gICAgICAgICAgdG90YWxDb3VudFxyXG4gICAgICAgIH1cclxuICAgICAgICBwcl9tZXJnZWQ6IHB1bGxSZXF1ZXN0cyhzdGF0ZXM6IE1FUkdFRCkge1xyXG4gICAgICAgICAgdG90YWxDb3VudFxyXG4gICAgICAgIH1cclxuICAgICAgICByZWxlYXNlcyB7XHJcbiAgICAgICAgICB0b3RhbENvdW50XHJcbiAgICAgICAgfVxyXG4gICAgICAgIGZvcmtDb3VudFxyXG4gICAgICAgIGxpY2Vuc2VJbmZvIHtcclxuICAgICAgICAgIHNwZHhJZFxyXG4gICAgICAgIH1cclxuICAgICAgfVxyXG4gICAgfVxyXG4gICAgcGFja2FnZXMge1xyXG4gICAgICB0b3RhbENvdW50XHJcbiAgICB9XHJcbiAgICBzdGFycmVkUmVwb3NpdG9yaWVzIHtcclxuICAgICAgdG90YWxDb3VudFxyXG4gICAgfVxyXG4gICAgd2F0Y2hpbmcge1xyXG4gICAgICB0b3RhbENvdW50XHJcbiAgICB9XHJcbiAgICBzcG9uc29yc2hpcHNBc1Nwb25zb3Ige1xyXG4gICAgICB0b3RhbENvdW50XHJcbiAgICB9XHJcbiAgICBzcG9uc29yc2hpcHNBc01haW50YWluZXIge1xyXG4gICAgICB0b3RhbENvdW50XHJcbiAgICB9XHJcbiAgICBjb250cmlidXRpb25zQ29sbGVjdGlvbiB7XHJcbiAgICAgIHRvdGFsUmVwb3NpdG9yaWVzV2l0aENvbnRyaWJ1dGVkQ29tbWl0c1xyXG4gICAgICB0b3RhbENvbW1pdENvbnRyaWJ1dGlvbnNcclxuICAgICAgcmVzdHJpY3RlZENvbnRyaWJ1dGlvbnNDb3VudFxyXG4gICAgICB0b3RhbElzc3VlQ29 Line truncated
query Gists {
user(login: "${e}") {
gists(last: 100) {
totalCount
nodes {
stargazerCount
isFork
forks {
totalCount
}
comments {
totalCount
}
}
}
}
}
`);console.debug(`metrics/compute/${e}/plugins > gists > processing ${a.nodes.length} gists`);let o=0,i=0,s=0;for(const e of a.nodes)e.isFork||(o+=e.stargazerCount,i+=e.forks.totalCount,s+=e.comments.totalCount);return{totalCount:a.totalCount,stargazers:o,forks:i,comments:s}}catch(e){throw{error:{message:"An error occured",instance:e}}}},habits:async function({login:e,rest:t,imports:r,data:n,q:a},{enabled:o=!1,from:i=100}={}){try{if(!o||!a.habits)return null;let{"habits.from":s=i.from??500,"habits.days":l=14,"habits.facts":d=!0,"habits.charts":p=!1}=a;s=_Mathmax(1,_Mathmin(1e3,+s)),l=_Mathmax(1,_Mathmin(30,+s));const c={facts:d,charts:p,commits:{hour:NaN,hours:{},day:NaN,days:{}},indents:{style:"",spaces:0,tabs:0},linguist:{available:!1,ordered:[],languages:{}}},u=_Mathceil(s/100),m=n.config.timezone?.offset??0;console.debug(`metrics/compute/${e}/plugins > habits > querying api`);const g=[];try{for(let r=0;r<u;r++)console.debug(`metrics/compute/${e}/plugins > habits > loading page ${r}`),g.push(...(await t.activity.listEventsForAuthenticatedUser({username:e,per_page:100,page:r})).data)}catch{console.debug(`metrics/compute/${e}/plugins > habits > no more page to load`)}console.debug(`metrics/compute/${e}/plugins > habits > ${g.length} events loaded`);const h=g.filter(({type:e})=>"PushEvent"===e).filter(({actor:t})=>t.login===e).filter(({created_at:e})=>new Date(e)>new Date(Date.now()-1e3*(60*(60*(24*l)))));console.debug(`metrics/compute/${e}/plugins > habits > filtered out ${h.length} push events over last ${l} days`),console.debug(`metrics/compute/${e}/plugins > habits > loading patches`);const f=[...(await Promise.allSettled(h.flatMap(({payload:e})=>e.commits).map(e=>e.url).map(async e=>(await t.request(e)).data.files)))].filter(({status:e})=>"fulfilled"===e).map(({value:e})=>e).flatMap(e=>e.map(e=>({name:r.paths.basename(e.filename),patch:e.patch??""}))).map(({name:e,patch:t})=>({name:e,patch:t.split("\n").filter(e=>/^[-+]/.test(e)).map(e=>e.substring(1)).join("\n")}));{console.debug(`metrics/compute/${e}/plugins > habits > searching most active day of week`);const t=h.map(({created_at:e})=>new Date(new Date(e).getTime()+m).getDay());for(const e of t)c.commits.days[e]=(c.commits.days[e]??0)+1;c.commits.days.max=_Mathmax(...Object.values(c.commits.days)),c.commits.day=t.length?["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][Object.entries(c.commits.days).sort(([e,t],[r,n])=>n-t).map(([e,t])=>e)[0]]??NaN:NaN}{console.debug(`metrics/compute/${e}/plugins > habits > searching most active time of day`);const t=h.map(({created_at:e})=>new Date(new Date(e).getTime()+m).getHours());for(const e of t)c.commits.hours[e]=(c.commits.hours[e]??0)+1;c.commits.hours.max=_Mathmax(...Object.values(c.commits.hours)),c.commits.hour=t.length?`${Object.entries(c.commits.hours).sort(([e,t],[r,n])=>n-t).map(([e,t])=>e)[0]}`.padStart(2,"0"):NaN}if(console.debug(`metrics/compute/${e}/plugins > habits > searching indent style`),f.map(({patch:e})=>e.match(/((?:\t)|(?: )) /gm)??[]).forEach(e=>c.indents[/^\t/.test(e)?"tabs":"spaces"]++),c.indents.style=c.indents.spaces>c.indents.tabs?"spaces":c.indents.tabs>c.indents.spaces?"tabs":"",p){console.debug(`metrics/compute/${e}/plugins > habits > searching recently used languages using linguist`);const t={win32:"wsl"}[process.platform]??"";if(f.length&&(await r.run(`${t} which github-linguist`))){c.linguist.available=!0;const n=r.paths.join(r.os.tmpdir(),`${h[0]?.actor?.id??0}`);console.debug(`metrics/compute/${e}/plugins > habits > creating temp dir ${n} with ${f.length} files`),await r.fs.mkdir(n,{recursive:!0}),await Promise.all(f.map(async({name:e,patch:t},a)=>await r.fs.writeFile(r.paths.join(n,`${a}${r.paths.extname(e)}`),t))),console.debug(`metrics/compute/${e}/plugins > habits > creating temp git repository`),await r.run(`git init && git add . && git config user.name "linguist" && git config user.email "null@github.com" && git commit -m "linguist"`,{cwd:n}).catch(console.debug),await r.run(`git status`,{cwd:n}),console.debug(`metrics/compute/${e}/plugins > habits > running linguist`),(await r.run(`${t} github-linguist --breakdown`,{cwd:n})).split("\n").map(e=>e.match(/(?<value>[\d.]+)%\s+(?<language>\w+)/)?.groups).filter(e=>e).map(({value:e,language:t})=>c.linguist.languages[t]=(c.linguist.languages[t]??0)+e/100),c.linguist.ordered=Object.entries(c.linguist.languages).sort(([e,t],[r,n])=>n-t)}else console.debug(`metrics/compute/${e}/plugins > habits > linguist not available`)}return c}catch(e){if(e.error?.message)throw e;throw{error:{message:"An error occured",instance:e}}}},isocalendar:async function({login:e,graphql:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.isocalendar)return null;let{"isocalendar.duration":a="half-year"}=r;a=["full-year","half-year"].includes(a)?a:"full-year";const o=new Date,s=new Date(o);"full-year"===a?s.setFullYear(o.getFullYear()-1):s.setHours(-4320);const l=new Date(s);l.setHours(-336),console.debug(`metrics/compute/${e}Line truncated
query Calendar {
user(login: "${e}") {
calendar:contributionsCollection(from: "${n.toISOString()}", to: "${a.toISOString()}") {
contributionCalendar {
weeks {
contributionDays {
contributionCount
color
date
}
}
}
}
}
}
`);d[r]=o}console.debug(`metrics/compute/${e}/plugins > isocalendar > applying padding`);const p=d.weeks[0].contributionDays,c=d.padding.flatMap(({contributionDays:e})=>e).filter(({date:e})=>!p.map(({date:e})=>e).includes(e));for(;7>p.length;)p.unshift(c.pop());console.debug(`metrics/compute/${e}/plugins > isocalendar > computing stats`);let u=0,m={max:0,current:0},g=[],h=0;for(const e of d.weeks)for(const t of e.contributionDays)g.push(t.contributionCount),u=_Mathmax(u,t.contributionCount),m.current=t.contributionCount?m.current+1:0,m.max=_Mathmax(m.max,m.current);h=(g.reduce((e,t)=>e+t,0)/g.length).toFixed(2).replace(/[.]0+$/,""),console.debug(`metrics/compute/${e}/plugins > isocalendar > computing svg render`);const f=6;let y=0,i=0,b=`
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="margin-top: -52px;" viewBox="0,0 480,${"full-year"===a?270:170}">
module.exports=(()=>{var _Mathhypot=Math.hypot,_Mathacos=Math.acos,_Mathtan=Math.tan,_Mathasin=Math.asin,_Mathsin=Math.sin,_Mathcos=Math.cos,_MathPI=Math.PI,_Mathsqrt=Math.sqrt,_NumberisInteger=Number.isInteger,_NumberPOSITIVE_INFINITY=Number.POSITIVE_INFINITY,_NumberNEGATIVE_INFINITY=Number.NEGATIVE_INFINITY,_Stringprototype=String.prototype,_NumberMAX_SAFE_INTEGER=Number.MAX_SAFE_INTEGER,_StringfromCharCode=String.fromCharCode,_Mathpow=Math.pow,_Mathabs=Math.abs,_Mathround=Math.round,_Mathfloor=Math.floor,_Mathceil=Math.ceil,_Mathmax=Math.max,_Mathmin=Math.min;function __webpack_require__(e){if(__webpack_module_cache__[e])return __webpack_module_cache__[e].exports;var t=__webpack_module_cache__[e]={exports:{}},r=!0;try{__webpack_modules__[e].call(t.exports,t,t.exports,__webpack_require__),r=!1}finally{r&&delete __webpack_module_cache__[e]}return t.exports}var __webpack_modules__={83994:(e,t,r)=>{"use strict";async function n({log:e=!0}={}){const t=e?console.debug:()=>null;t(`metrics/setup > setup`);const n="src/templates",a="src/queries",o={templates:{},queries:{},settings:{},statics:r.ab+"html",node_modules:b.resolve("node_modules")};if(t(`metrics/setup > load settings.json`),y.existsSync(r.ab+"settings.json")?(o.settings=JSON.parse(`${await y.promises.readFile(b.resolve("settings.json"))}`),t(`metrics/setup > load settings.json > success`)):t(`metrics/setup > load settings.json > (missing)`),o.settings.templates||(o.settings.templates={default:"classic",enabled:[]}),o.settings.plugins||(o.settings.plugins={}),o.settings.plugins.base={parts:["header","activity","community","repositories","metadata"]},o.settings.debug&&t(S.inspect(o.settings,{depth:1/0,maxStringLength:256})),t(`metrics/setup > load package.json`),y.existsSync(r.ab+"package.json")?(o.package=JSON.parse(`${await y.promises.readFile(b.resolve("package.json"))}`),t(`metrics/setup > load package.json > success`)):(t(`metrics/setup > load package.json > (missing)`),o.package={version:"2.10.0-beta",author:"lowlighter"}),y.existsSync(r.ab+"templates"))for(const e of await y.promises.readdir(n)){if(/.*[.]mjs$/.test(e))continue;t(`metrics/setup > load template [${e}]`);const r=[`${n}/${e}/image.svg`,`${n}/${e}/style.css`,`${n}/${e}/fonts.css`].map(t=>y.existsSync(b.resolve(t))?t:t.replace(`${n}/${e}/`,`${n}/classic/`)).map(e=>b.resolve(e)),[a,i,s]=await Promise.all(r.map(async e=>`${await y.promises.readFile(e)}`));o.templates[e]={image:a,style:i,fonts:s},t(`metrics/setup > load template [${e}] > success`),o.settings.debug&&Object.defineProperty(o.templates,e,{get(){t(`metrics/setup > reload template [${e}]`);const[n,a,o]=r.map(e=>`${y.readFileSync(e)}`);return t(`metrics/setup > reload template [${e}] > success`),{image:n,style:a,fonts:o}}})}else t(`metrics/setup > load templates from build`),o.templates=JSON.parse(Buffer.from(`eyJjbGFzc2ljIjp7ImltYWdlIjoiPHN2ZyB4bWxucz1cImh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnXCIgd2lkdGg9XCI0ODBcIiBoZWlnaHQ9XCI8JT0gMTJcclxuICArICghIWJhc2UuaGVhZGVyKSo4MCArICh1c2VyLmlzSGlyZWFibGUpKjE2XHJcbiAgKyAoISFiYXNlLm1ldGFkYXRhKSozOFxyXG4gICsgKCghIWJhc2UuYWN0aXZpdHkpfHwoISFiYXNlLmNvbW11bml0eSkpKjEyOFxyXG4gICsgKCEhYmFzZS5yZXBvc2l0b3JpZXMpKjEwOFxyXG4gICsgKCghIWJhc2UucmVwb3NpdG9yaWVzKSooKCEhcGx1Z2lucy50cmFmZmljKXx8KCEhcGx1Z2lucy5saW5lcykpKSoxNlxyXG4gICsgKCEhcGx1Z2lucy5mb2xsb3d1cCkqNjhcclxuICArICghIXBsdWdpbnMucGFnZXNwZWVkKSoxMjYgKyAocGx1Z2lucy5wYWdlc3BlZWQ/LmRldGFpbGVkID8/IDApKjYqMjAgKyAoISFwbHVnaW5zLnBhZ2VzcGVlZD8uc2NyZWVuc2hvdCkqMzMwXHJcbiAgKyAoISFwbHVnaW5zLmhhYml0cykqMjggKyAoISFwbHVnaW5zLmhhYml0cz8uZmFjdHMpKjU4ICsgKCEhcGx1Z2lucy5oYWJpdHM/LmNoYXJ0cykqMjI2XHJcbiAgKyAoISFwbHVnaW5zLmxhbmd1YWdlcykqOTZcclxuICArICghIXBsdWdpbnMubXVzaWMpKjY0ICsgKHBsdWdpbnMubXVzaWM/LnRyYWNrcz8ubGVuZ3RoID8gMTQrTWF0aC5tYXgoMCwgcGx1Z2lucy5tdXNpYy50cmFja3MubGVuZ3RoLTEpKjM2IDogMClcclxuICArICghIXBsdWdpbnMucG9zdHMpKjY0ICsgKHBsdWdpbnMucG9zdHM/Lmxpc3Q/Lmxlbmd0aCA/PyAwKSo0MFxyXG4gICsgKCEhcGx1Z2lucy5pc29jYWxlbmRhcikqMTkyICsgKHBsdWdpbnMuaXNvY2FsZW5kYXI/LmR1cmF0aW9uID09PSAnZnVsbC15ZWFyJykqMTAwXHJcbiAgKyAoISFwbHVnaW5zLmdpc3RzKSo2OFxyXG4gICsgKCEhcGx1Z2lucy50b3BpY3MpKjE2MFxyXG4gICsgKCEhcGx1Z2lucy5wcm9qZWN0cykqMjIgKyAocGx1Z2lucy5wcm9qZWN0cz8ubGlzdD8ubGVuZ3RoID8/IDApKjYwICsgKCEhcGx1Z2lucy5wcm9qZWN0cz8uZXJyb3IpKjIyXHJcbiAgKyAoISFwbHVnaW5zLnR3ZWV0cykqNjQgKyAocGx1Z2lucy50d2VldHM/Lmxpc3Q/Lmxlbmd0aCA/PyAwKSo5MFxyXG4gICsgTWF0aC5tYXgoMCwgKCgoISFiYXNlLm1ldGFkYXRhKSsoISFiYXNlLmhlYWRlcikrKCghIWJhc2UuYWN0aXZpdHkpfHwoISFiYXNlLmNvbW11bml0eSkpKyghIWJhc2UucmVwb3NpdG9yaWVzKSsoKCEhcGx1Z2lucy5oYWJpdHMpKSsoISFwbHVnaW5zLnBhZ2VzcGVlZCkrKCEhcGx1Z2lucy5sYW5ndWFnZXMpKyghIXBsdWdpbnMubXVzaWMpKyghIXBsdWdpbnMucG9zdHMpKyghIXBsdWdpbnMuaXNvY2FsZW5kYXIpKyghIXBsdWdpbnMuZ2lzdHMpKyghIXBsdWdpbnMudG9waWNzKSsoISFwbHVnaW5zLnByb2plY3RzKSktMSkpKjRcclxuJT5cIj5cclxuXHJcbiAgPGRlZnM+PHN0eWxlPjwlPSBmb250cyAlPjwvc3R5bGU+PC9kZWZzPlxyXG5cclxuICA8c3R5bGU+XHJcbiAgICA8JT0gc3R5bGUgJT5cclxuICA8L3N0eWxlPlxyXG5cclxuICA8Zm9yZWlnbk9iamVjdCB4PVwiMFwiIHk9XCIwXCIgd2lkdGg9XCIxMDAlXCIgaGVpZ2h0PVwiMTAwJVwiPlxyXG4gICAgPGRpdiB4bWxucz1cImh0 Line truncated
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="margin-top: -52px;" viewBox="0,0 480,${"full-year"===o?270:170}">
${[1,2].map(e=>`
<filter id="brightness${e}">
<feComponentTransfer>
${[..."RGB"].map(t=>`<feFunc${t} type="linear" slope="${1-.4*e}" />`).join("")}
</feComponentTransfer>
</filter>`).join("")}
<g transform="scale(4) translate(12, 0)">`;for(const e of d.weeks){b+=`<g transform="translate(${1.7*y}, ${y})">`,i=0;for(const t of e.contributionDays){const e=t.contributionCount/u;b+=`
<g transform="translate(${-1.7*i}, ${i+(1-e)*f})">
<g transform="scale(4) translate(12, 0)">`;for(const e of p.weeks){S+=`<g transform="translate(${1.7*b}, ${b})">`,i=0;for(const t of e.contributionDays){const e=t.contributionCount/m;S+=`
<g transform="translate(${-1.7*i}, ${i+(1-e)*y})">
<path fill="${t.color}" d="M1.7,2 0,1 1.7,0 3.4,1 z" />
<path fill="${t.color}" filter="url(#brightness1)" d="M0,1 1.7,2 1.7,${2+e*f} 0,${1+e*f} z" />
<path fill="${t.color}" filter="url(#brightness2)" d="M1.7,2 3.4,1 3.4,${1+e*f} 1.7,${2+e*f} z" />
</g>`,i++}b+=`</g>`,y++}return b+=`</g></svg>`,{streak:m,max:u,average:h,svg:b,duration:a}}catch(e){throw{error:{message:"An error occured",instance:e}}}},languages:async function({login:e,data:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.languages)return null;let{"languages.ignored":a="","languages.skipped":o=""}=r;a=decodeURIComponent(a).split(",").map(e=>e.trim().toLocaleLowerCase()).filter(e=>e),o=decodeURIComponent(o).split(",").map(e=>e.trim().toLocaleLowerCase()).filter(e=>e),console.debug(`metrics/compute/${e}/plugins > languages > processing ${t.user.repositories.nodes.length} repositories`);const s={colors:{},total:0,stats:{}};for(const r of t.user.repositories.nodes){if(o.includes(r.name.toLocaleLowerCase())){console.debug(`metrics/compute/${e}/plugins > languages > skipped repository ${r.name}`);continue}for(const{size:t,node:{color:n,name:o}}of Object.values(r.languages.edges)){if(a.includes(o.toLocaleLowerCase())){console.debug(`metrics/compute/${e}/plugins > languages > ignored language ${o}`);continue}s.stats[o]=(s.stats[o]??0)+t,s.colors[o]=n??"#ededed",s.total+=t}}console.debug(`metrics/compute/${e}/plugins > languages > computing stats`),Object.keys(s.stats).map(e=>s.stats[e]/=s.total),s.favorites=Object.entries(s.stats).sort(([e,t],[r,n])=>n-t).slice(0,8).map(([e,t])=>({name:e,value:t,color:s.colors[e],x:0}));for(let e=1;e<s.favorites.length;e++)s.favorites[e].x=s.favorites[e-1].x+s.favorites[e-1].value;return s}catch(e){throw{error:{message:"An error occured",instance:e}}}},lines:async function({login:e,data:t,imports:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.lines)return null;const i=t.user.repositories.nodes.map(({name:e})=>e)??[];console.debug(`metrics/compute/${e}/plugins > lines > querying api`);const s={added:0,deleted:0},l=await Promise.all(i.map(async t=>await n.repos.getContributorsStats({owner:e,repo:t})));return console.debug(`metrics/compute/${e}/plugins > lines > computing total diff`),l.map(({data:t})=>{if(Array.isArray(t)){const[r]=t.filter(({author:t})=>t.login===e);r&&r.weeks.forEach(({a:e,d:t})=>(s.added+=e,s.deleted+=t))}}),s.added=r.format(s.added),s.deleted=r.format(s.deleted),s}catch(e){throw{error:{message:"An error occured",instance:e}}}},music:a,pagespeed:async function({login:e,imports:t,data:r,q:n},{enabled:a=!1,token:o=null}={}){try{if(!a||!n.pagespeed||!r.user.websiteUrl)return null;let{"pagespeed.detailed":i=!1}=n;i=!!i;let s=r.user.websiteUrl;/^https?:[/][/]/.test(s)||(s=`https://${s}`);const l={url:s,detailed:i,scores:[],metrics:{}};console.debug(`metrics/compute/${e}/plugins > pagespeed > querying api for ${s}`);const d=new Map;if(await Promise.all(["performance","accessibility","best-practices","seo"].map(async r=>{console.debug(`metrics/compute/${e}/plugins > pagespeed > performing audit ${r}`);const n=await t.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=${r}&url=${s}&key=${o}`);console.debug(n.data);const{score:a,title:i}=n.data.lighthouseResult.categories[r];d.set(r,{score:a,title:i}),console.debug(`metrics/compute/${e}/plugins > pagespeed > performed audit ${r} (status code ${n.status})`)})),l.scores=[d.get("performance"),d.get("accessibility"),d.get("best-practices"),d.get("seo")],i){console.debug(`metrics/compute/${e}/plugins > pagespeed > performing detailed audit`);const r=await t.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?&url=${s}&key=${o}`);console.debug(r.data),Object.assign(l.metrics,...r.data.lighthouseResult.audits.metrics.details.items),console.debug(`metrics/compute/${e}/plugins > pagespeed > performed detailed audit (status code ${r.status})`)}return l}catch(e){let t="An error occured";if(e.isAxiosError){const r=e.response?.status,n=e.response?.data?.error?.message?.match(/Lighthouse returned error: (?<description>[A-Z_]+)/)?.groups?.description??null;t=`API returned ${r}${n?` (${n})`:""}`,e=e.response?.data??null}throw{error:{message:t,instance:e}}}},posts:async function({imports:e,data:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.posts)return null;const a=t.user.login;let{"posts.source":o="","posts.limit":i=4}=r;i=_Mathmax(1,_Mathmin(30,+i)),console.debug(`metrics/compute/${a}/plugins > posts > processing with source ${o}`);let s=null;switch(o){case"dev.to":{console.debug(`metrics/compute/${a}/plugins > posts > querying api`),s=(await e.axios.get(`https://dev.to/api/articles?username=${a}&state=fresh`)).data.map(({title:e,readable_publish_date:t})=>({title:e,date:t}));break}default:throw{error:{message:`Unsupported source "${o}"`}};}if(Array.isArray(s))return 0<i&&(console.debug(`metrics/compute/${a}/plugins > posts > keeping only ${i} posts`),s=s.slice(0,i)),{source:o,list:s};throw{error:{message:`An error occured (could not retrieve posts)`}}}catch(e){throw{error:{message:"An error occured",instance:e}}}},projects:async function({login:e,graphql:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.projects)return null;let{"projects.limit":a=4}=r;a=_Mathmax(1,_Mathmin(1Line truncated
query Projects {
user(login: "${e}") {
projects(last: ${a}, states: OPEN, orderBy: {field: UPDATED_AT, direction: DESC}) {
totalCount
nodes {
name
updatedAt
progress {
doneCount
inProgressCount
todoCount
enabled
}
}
}
}
}
`);console.debug(`metrics/compute/${e}/plugins > posts > processing ${o.nodes.length} projects`);const i=[];for(const e of o.nodes){const t=(Date.now()-new Date(e.updatedAt).getTime())/86400000;let r=1>t?"less than 1 day ago":30>t?`${_Mathfloor(t)} day${2<=t?"s":""} ago`:new Date(e.updatedAt).toDateString().substring(4);const{enabled:n,todoCount:a,inProgressCount:o,doneCount:s}=e.progress;i.push({name:e.name,updated:r,progress:{enabled:n,todo:a,doing:o,done:s,total:a+o+s}})}return{list:i,totalCount:o.totalCount}}catch(e){let t="An error occured";throw e.errors?.map(({type:e})=>e)?.includes("INSUFFICIENT_SCOPES")&&(t="Insufficient token rights"),{error:{message:t,instance:e}}}},topics:async function({login:e,imports:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.topics)return null;let{"topics.sort":a="stars","topics.limit":o=15}=r;const i="random"===a;a={starred:"created",activity:"updated",stars:"stars",random:"created"}[a]??"starred",o=_Mathmax(1,_Mathmin(20,+o)),console.debug(`metrics/compute/${e}/plugins > topics > searching starred topics`);let s=[];console.debug(`metrics/compute/${e}/plugins > topics > starting browser`);const l=await t.puppeteer.launch({headless:!0,executablePath:process.env.PUPPETEER_BROWSER_PATH,args:["--no-sandbox","--disable-extensions","--disable-setuid-sandbox","--disable-dev-shm-usage"]});console.debug(`metrics/compute/${e}/plugins > topics > started ${await l.version()}`);const d=await l.newPage();for(let t=1;100>=t;t++){console.debug(`metrics/compute/${e}/plugins > topics > loading page ${t}`),await d.goto(`https://github.com/stars/${e}/topics?direction=desc&page=${t}&sort=${a}`);const r=d.mainFrame();await Promise.race([r.waitForSelector("ul.repo-list"),r.waitForSelector(".blankslate")]);const n=await r.evaluate(()=>[...document.querySelectorAll("ul.repo-list li")].map(e=>({name:e.querySelector(".f3").innerText,description:e.querySelector(".f5").innerText,icon:e.querySelector("img")?.src??null})));if(console.debug(`metrics/compute/${e}/plugins > topics > extracted ${n.length} starred topics`),!n.length){console.debug(`metrics/compute/${e}/plugins > topics > no more page to load`);break}s.push(...n)}if(console.debug(`metrics/compute/${e}/plugins > music > closing browser`),await l.close(),i&&(console.debug(`metrics/compute/${e}/plugins > topics > shuffling topics`),s=t.shuffle(s)),0<o){console.debug(`metrics/compute/${e}/plugins > topics > keeping only ${o} topics`);const t=s.slice(o);s=s.slice(0,o),s.push({name:`And ${t.length} more...`,description:t.map(({name:e})=>e).join(", "),icon:null})}console.debug(`metrics/compute/${e}/plugins > topics > loading artworks`);for(const r of s)r.icon&&(console.debug(`metrics/compute/${e}/plugins > topics > processing ${r.name}`),r.icon=await t.imgb64(r.icon)),r.description=t.htmlescape(r.description);return{list:s}}catch(e){throw{error:{message:"An error occured",instance:e}}}},traffic:async function({login:e,imports:t,data:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.traffic)return null;const i=r.user.repositories.nodes.map(({name:e})=>e)??[];console.debug(`metrics/compute/${e}/plugins > traffic > querying api`);const s={count:0,uniques:0},l=await Promise.all(i.map(async t=>await n.repos.getViews({owner:e,repo:t})));return console.debug(`metrics/compute/${e}/plugins > traffic > computing stats`),l.filter(({data:e})=>e).map(({data:{count:e,uniques:t}})=>(s.count+=e,s.uniques+=t)),s.count=t.format(s.count),s.uniques=t.format(s.uniques),{views:s}}catch(e){let t="An error occured";throw 403===e.status&&(t="Insufficient token rights"),{error:{message:t,instance:e}}}},tweets:async function({login:e,imports:t,data:r,q:n},{enabled:a=!1,token:o=null}={}){try{if(!a||!n.tweets)return null;let{"tweets.limit":i=2}=n;i=_Mathmax(1,_Mathmin(10,+i));const s=r.user.twitterUsername;console.debug(`metrics/compute/${e}/plugins > tweets > loading twitter profile (@${s})`);const{data:{data:l=null}}=await t.axios.get(`https://api.twitter.com/2/users/by/username/${s}?user.fields=profile_image_url,verified`,{headers:{Authorization:`Bearer ${o}`}});console.debug(`metrics/compute/${e}/plugins > tweets > querying api`);const{data:{data:d=[]}}=await t.axios.get(`https://api.twitter.com/2/tweets/search/recent?query=from:${s}&tweet.fields=created_at&expansions=entities.mentions.username`,{headers:{Authorization:`Bearer ${o}`}});return l?.profile_image_url&&(console.debug(`metrics/compute/${e}/plugins > tweets > loading profile image`),l.profile_image=await t.imgb64(l.profile_image_url)),0<i&&(console.debug(`metrics/compute/${e}/plugins > tweets > keeping only ${i} tweets`),d.splice(i)),await Promise.all(d.map(async r=>{r.mentions=r.entities?.mentions.map(({username:e})=>e)??[],console.debug(`metrics/compute/${e}/plugins > tweets > formatting tweet ${r.id}`),r.text=t.htmlescape(t.htmlescape(r.text,{"<":!0,">":!0}).replace(new RegExp(`@(${r.mentions.join("|")})`,"gi"),` <span class="mention">@$1</span> `).replace(/(?<!&)[#|#]([a-z0-9_\u00c0-\u00d6\u00d8-\u00f6 Line truncated
<path fill="${t.color}" filter="url(#brightness1)" d="M0,1 1.7,2 1.7,${2+e*y} 0,${1+e*y} z" />
<path fill="${t.color}" filter="url(#brightness2)" d="M1.7,2 3.4,1 3.4,${1+e*y} 1.7,${2+e*y} z" />
</g>`,i++}S+=`</g>`,b++}return S+=`</g></svg>`,{streak:g,max:m,average:f,svg:S,duration:o}}catch(e){throw{error:{message:"An error occured",instance:e}}}},languages:async function({login:e,data:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.languages)return null;let{"languages.ignored":a="","languages.skipped":o=""}=r;a=decodeURIComponent(a).split(",").map(e=>e.trim().toLocaleLowerCase()).filter(e=>e),o=decodeURIComponent(o).split(",").map(e=>e.trim().toLocaleLowerCase()).filter(e=>e),console.debug(`metrics/compute/${e}/plugins > languages > processing ${t.user.repositories.nodes.length} repositories`);const s={colors:{},total:0,stats:{}};for(const r of t.user.repositories.nodes){if(o.includes(r.name.toLocaleLowerCase())){console.debug(`metrics/compute/${e}/plugins > languages > skipped repository ${r.name}`);continue}for(const{size:t,node:{color:n,name:o}}of Object.values(r.languages.edges)){if(a.includes(o.toLocaleLowerCase())){console.debug(`metrics/compute/${e}/plugins > languages > ignored language ${o}`);continue}s.stats[o]=(s.stats[o]??0)+t,s.colors[o]=n??"#ededed",s.total+=t}}console.debug(`metrics/compute/${e}/plugins > languages > computing stats`),Object.keys(s.stats).map(e=>s.stats[e]/=s.total),s.favorites=Object.entries(s.stats).sort(([e,t],[r,n])=>n-t).slice(0,8).map(([e,t])=>({name:e,value:t,color:s.colors[e],x:0}));for(let e=1;e<s.favorites.length;e++)s.favorites[e].x=s.favorites[e-1].x+s.favorites[e-1].value;return s}catch(e){throw{error:{message:"An error occured",instance:e}}}},lines:async function({login:e,data:t,imports:r,rest:n,q:a},{enabled:o=!1}={}){try{if(!o||!a.lines)return null;const i=t.user.repositories.nodes.map(({name:e})=>e)??[];console.debug(`metrics/compute/${e}/plugins > lines > querying api`);const s={added:0,deleted:0},l=await Promise.all(i.map(async t=>await n.repos.getContributorsStats({owner:e,repo:t})));return console.debug(`metrics/compute/${e}/plugins > lines > computing total diff`),l.map(({data:t})=>{if(Array.isArray(t)){const[r]=t.filter(({author:t})=>t.login===e);r&&r.weeks.forEach(({a:e,d:t})=>(s.added+=e,s.deleted+=t))}}),s.added=r.format(s.added),s.deleted=r.format(s.deleted),s}catch(e){throw{error:{message:"An error occured",instance:e}}}},music:a,pagespeed:async function({login:e,imports:t,data:r,q:n},{enabled:a=!1,token:o=null}={}){try{if(!a||!n.pagespeed||!r.user.websiteUrl)return null;let{"pagespeed.detailed":i=!1,"pagespeed.screenshot":s=!1}=n;i=!!i;let l=r.user.websiteUrl;/^https?:[/][/]/.test(l)||(l=`https://${l}`);const d={url:l,detailed:i,scores:[],metrics:{}};console.debug(`metrics/compute/${e}/plugins > pagespeed > querying api for ${l}`);const p=new Map;if(await Promise.all(["performance","accessibility","best-practices","seo"].map(async r=>{console.debug(`metrics/compute/${e}/plugins > pagespeed > performing audit ${r}`);const n=await t.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=${r}&url=${l}&key=${o}`);console.debug(n.data);const{score:a,title:i}=n.data.lighthouseResult.categories[r];p.set(r,{score:a,title:i}),console.debug(`metrics/compute/${e}/plugins > pagespeed > performed audit ${r} (status code ${n.status})`),s&&"performance"===r&&(d.screenshot=n.data.lighthouseResult.audits["final-screenshot"].details.data,console.debug(`metrics/compute/${e}/plugins > pagespeed > performed audit ${r} (status code ${n.status})`))})),d.scores=[p.get("performance"),p.get("accessibility"),p.get("best-practices"),p.get("seo")],i){console.debug(`metrics/compute/${e}/plugins > pagespeed > performing detailed audit`);const r=await t.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?&url=${l}&key=${o}`);console.debug(r.data),Object.assign(d.metrics,...r.data.lighthouseResult.audits.metrics.details.items),console.debug(`metrics/compute/${e}/plugins > pagespeed > performed detailed audit (status code ${r.status})`)}return d}catch(e){let t="An error occured";if(e.isAxiosError){const r=e.response?.status,n=e.response?.data?.error?.message?.match(/Lighthouse returned error: (?<description>[A-Z_]+)/)?.groups?.description??null;t=`API returned ${r}${n?` (${n})`:""}`,e=e.response?.data??null}throw{error:{message:t,instance:e}}}},posts:async function({imports:e,data:t,q:r},{enabled:n=!1}={}){try{if(!n||!r.posts)return null;const a=t.user.login;let{"posts.source":o="","posts.limit":i=4}=r;i=_Mathmax(1,_Mathmin(30,+i)),console.debug(`metrics/compute/${a}/plugins > posts > processing with source ${o}`);let s=null;switch(o){case"dev.to":{console.debug(`metrics/compute/${a}/plugins > posts > querying api`),s=(await e.axios.get(`https://dev.to/api/articles?username=${a}&state=fresh`)).data.map(({title:e,readable_publish_date:t})=>({title:e,date:t}));break}default:throw{error:{message:`Unsupported source "${o}"`}};}if(Array.isArray(s))return 0<i&&(console.debug(`metrics/compute/${a}/plugins > posts > keeping only ${i} posts`),s.splice(i)),{source:o,list:s};throw{error:{message:`An error occured (could not retLine truncated
query Sha {
repository(owner: "${t.context.repo.owner}", name: "${t.context.repo.repo}") {
object(expression: "${r}:${m}") { ... on Blob { oid } }
}
}
`);o=e}catch(e){console.debug(e)}console.log(`Previous render sha | ${o??"(none)"}`),await a.repos.createOrUpdateFileContents({...t.context.repo,path:m,message:`Update ${m} - [Skip GitHub Action]`,content:Buffer.from(T).toString("base64"),...(o?{sha:o}:{})}),console.log(`Commit to repo | ok`)}console.log(`Success, thanks for using metrics !`),process.exit(0)}catch(t){if(console.error(t),!i(e.getInput("debug")))for(const e of["_".repeat(64),"An error occured, logging debug message :",...s])console.log(e);e.setFailed(t.message),process.exit(1)}})().catch(()=>process.exit(1))},12541:function(e,t,r){"use strict";function n(e,t,r){const n=new p(e,t,r);process.stdout.write(n.toString()+s.EOL)}function a(e){return l.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function o(e){return l.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}var i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t};Object.defineProperty(t,"__esModule",{value:!0});const s=i(r(12087)),l=r(74332);t.issueCommand=n,t.issue=function(e,t=""){n(e,{},t)};const d="::";class p{constructor(e,t,r){e||(e="missing.command"),this.command=e,this.properties=t,this.message=r}toString(){let e=d+this.command;if(this.properties&&0<Object.keys(this.properties).length){e+=" ";let t=!0;for(const r in this.properties)if(this.properties.hasOwnProperty(r)){const n=this.properties[r];n&&(t?t=!1:e+=",",e+=`${r}=${o(n)}`)}}return e+=`${d}${a(this.message)}`,e}}},32882:function(e,t,r){"use strict";function n(e){process.exitCode=g.Failure,a(e)}function a(e){d.issue("error",e instanceof Error?e.toString():e)}function o(e){d.issue("group",e)}function i(){d.issue("endgroup")}var s=this&&this.__awaiter||function(e,t,r,n){function a(e){return e instanceof r?e:new r(function(t){t(e)})}return new(r||(r=Promise))(function(r,o){function i(e){try{l(n.next(e))}catch(t){o(t)}}function s(e){try{l(n["throw"](e))}catch(t){o(t)}}function l(e){e.done?r(e.value):a(e.value).then(i,s)}l((n=n.apply(e,t||[])).next())})},l=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t};Object.defineProperty(t,"__esModule",{value:!0});const d=r(12541),p=r(29582),c=r(74332),u=l(r(12087)),m=l(r(85622));var g;(function(e){e[e.Success=0]="Success",e[e.Failure=1]="Failure"})(g=t.ExitCode||(t.ExitCode={})),t.exportVariable=function(e,t){const r=c.toCommandValue(t);process.env[e]=r;const n=process.env.GITHUB_ENV||"";if(n){const t=`${e}<<${"_GitHubActionsFileCommandDelimeter_"}${u.EOL}${r}${u.EOL}${"_GitHubActionsFileCommandDelimeter_"}`;p.issueCommand("ENV",t)}else d.issueCommand("set-env",{name:e},r)},t.setSecret=function(e){d.issueCommand("add-mask",{},e)},t.addPath=function(e){const t=process.env.GITHUB_PATH||"";t?p.issueCommand("PATH",e):d.issueCommand("add-path",{},e),process.env.PATH=`${e}${m.delimiter}${process.env.PATH}`},t.getInput=function(e,t){const r=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(t&&t.required&&!r)throw new Error(`Input required and not supplied: ${e}`);return r.trim()},t.setOutput=function(e,t){d.issueCommand("set-output",{name:e},t)},t.setCommandEcho=function(e){d.issue("echo",e?"on":"off")},t.setFailed=n,t.isDebug=function(){return"1"===process.env.RUNNER_DEBUG},t.debug=function(e){d.issueCommand("debug",{},e)},t.error=a,t.warning=function(e){d.issue("warning",e instanceof Error?e.toString():e)},t.info=function(e){process.stdout.write(e+u.EOL)},t.startGroup=o,t.endGroup=i,t.group=function(e,t){return s(this,void 0,void 0,function*(){o(e);let r;try{r=yield t()}finally{i()}return r})},t.saveState=function(e,t){d.issueCommand("save-state",{name:e},t)},t.getState=function(e){return process.env[`STATE_${e}`]||""}},29582:function(e,t,r){"use strict";var n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t};Object.defineProperty(t,"__esModule",{value:!0});const a=n(r(35747)),o=n(r(12087)),i=r(74332);t.issueCommand=function(e,t){const r=process.env[`GITHUB_${e}`];if(!r)throw new Error(`Unable to find environment variable for file command ${e}`);if(!a.existsSync(r))throw new Error(`Missing file at path: ${r}`);a.appendFileSync(r,`${i.toCommandValue(t)}${o.EOL}`,{encoding:"utf8"})}},74332:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.toCommandValue=function(e){return null===e||void 0===e?"":"string"==typeof e||e instanceof String?e:JSON.stringify(e)}},84873:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Context=void 0;const n=r(35747),a=r(12087);class o{constructor(){if(this.payload={},process.env.GITHUB_EVENT_PATH)if(n.existsSync(process.e Line truncated
`);o=e}catch(e){console.debug(e)}console.log(`Previous render sha | ${o??"(none)"}`),await a.repos.createOrUpdateFileContents({...t.context.repo,path:m,message:`Update ${m} - [Skip GitHub Action]`,content:Buffer.from(E).toString("base64"),...(o?{sha:o}:{})}),console.log(`Commit to repo | ok`)}console.log(`Success, thanks for using metrics !`),process.exit(0)}catch(t){if(console.error(t),!i(e.getInput("debug")))for(const e of["_".repeat(64),"An error occured, logging debug message :",...s])console.log(e);e.setFailed(t.message),process.exit(1)}})().catch(()=>process.exit(1))},12541:function(e,t,r){"use strict";function n(e,t,r){const n=new p(e,t,r);process.stdout.write(n.toString()+s.EOL)}function a(e){return l.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function o(e){return l.toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}var i=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t};Object.defineProperty(t,"__esModule",{value:!0});const s=i(r(12087)),l=r(74332);t.issueCommand=n,t.issue=function(e,t=""){n(e,{},t)};const d="::";class p{constructor(e,t,r){e||(e="missing.command"),this.command=e,this.properties=t,this.message=r}toString(){let e=d+this.command;if(this.properties&&0<Object.keys(this.properties).length){e+=" ";let t=!0;for(const r in this.properties)if(this.properties.hasOwnProperty(r)){const n=this.properties[r];n&&(t?t=!1:e+=",",e+=`${r}=${o(n)}`)}}return e+=`${d}${a(this.message)}`,e}}},32882:function(e,t,r){"use strict";function n(e){process.exitCode=g.Failure,a(e)}function a(e){d.issue("error",e instanceof Error?e.toString():e)}function o(e){d.issue("group",e)}function i(){d.issue("endgroup")}var s=this&&this.__awaiter||function(e,t,r,n){function a(e){return e instanceof r?e:new r(function(t){t(e)})}return new(r||(r=Promise))(function(r,o){function i(e){try{l(n.next(e))}catch(t){o(t)}}function s(e){try{l(n["throw"](e))}catch(t){o(t)}}function l(e){e.done?r(e.value):a(e.value).then(i,s)}l((n=n.apply(e,t||[])).next())})},l=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t};Object.defineProperty(t,"__esModule",{value:!0});const d=r(12541),p=r(29582),c=r(74332),u=l(r(12087)),m=l(r(85622));var g;(function(e){e[e.Success=0]="Success",e[e.Failure=1]="Failure"})(g=t.ExitCode||(t.ExitCode={})),t.exportVariable=function(e,t){const r=c.toCommandValue(t);process.env[e]=r;const n=process.env.GITHUB_ENV||"";if(n){const t=`${e}<<${"_GitHubActionsFileCommandDelimeter_"}${u.EOL}${r}${u.EOL}${"_GitHubActionsFileCommandDelimeter_"}`;p.issueCommand("ENV",t)}else d.issueCommand("set-env",{name:e},r)},t.setSecret=function(e){d.issueCommand("add-mask",{},e)},t.addPath=function(e){const t=process.env.GITHUB_PATH||"";t?p.issueCommand("PATH",e):d.issueCommand("add-path",{},e),process.env.PATH=`${e}${m.delimiter}${process.env.PATH}`},t.getInput=function(e,t){const r=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(t&&t.required&&!r)throw new Error(`Input required and not supplied: ${e}`);return r.trim()},t.setOutput=function(e,t){d.issueCommand("set-output",{name:e},t)},t.setCommandEcho=function(e){d.issue("echo",e?"on":"off")},t.setFailed=n,t.isDebug=function(){return"1"===process.env.RUNNER_DEBUG},t.debug=function(e){d.issueCommand("debug",{},e)},t.error=a,t.warning=function(e){d.issue("warning",e instanceof Error?e.toString():e)},t.info=function(e){process.stdout.write(e+u.EOL)},t.startGroup=o,t.endGroup=i,t.group=function(e,t){return s(this,void 0,void 0,function*(){o(e);let r;try{r=yield t()}finally{i()}return r})},t.saveState=function(e,t){d.issueCommand("save-state",{name:e},t)},t.getState=function(e){return process.env[`STATE_${e}`]||""}},29582:function(e,t,r){"use strict";var n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var r in e)Object.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t["default"]=e,t};Object.defineProperty(t,"__esModule",{value:!0});const a=n(r(35747)),o=n(r(12087)),i=r(74332);t.issueCommand=function(e,t){const r=process.env[`GITHUB_${e}`];if(!r)throw new Error(`Unable to find environment variable for file command ${e}`);if(!a.existsSync(r))throw new Error(`Missing file at path: ${r}`);a.appendFileSync(r,`${i.toCommandValue(t)}${o.EOL}`,{encoding:"utf8"})}},74332:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.toCommandValue=function(e){return null===e||void 0===e?"":"string"==typeof e||e instanceof String?e:JSON.stringify(e)}},84873:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Context=void 0;const n=r(35747),a=r(12087);class o{constructor(){if(this.payload={},process.env.GITHUB_EVENT_PATH)if(n.existsSync(process.e Line truncated
* @file Embedded JavaScript templating engine. {@link http://ejs.co}
* @author Matthew Eernisse <mde@fleegix.org>
* @author Tiancheng "Timothy" Gu <timothygu99@gmail.com>
@@ -172,4 +120,4 @@ If you think this is a bug, please report it on the Puppeteer issue tracker.`}\n
* ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО
* ОБЕСПЕЧЕНИЯ
* ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
*/function n(e,t){return e.renameElem("use"),e.removeAttr("d"),e.removeAttr("stroke"),e.removeAttr("fill"),e.addAttr({name:"xlink:href",local:"xlink:href",prefix:"none",value:"#"+t}),delete e.pathJS,e}function a(e,t){if(!e.isEmpty())for(let r of e.content)t(r),a(r,t)}var o=r(71921);t.type="full",t.active=!1,t.description="Finds <path> elements with the same d, fill, and stroke, and converts them to <use> elements referencing a single <path> def.",t.fn=function(e){const t=new Map;let r=0;const i=[];a(e,e=>{if(!e.isElem("path")||!e.hasAttr("d"))return;const a=e.attr("d").value,o=e.hasAttr("fill")&&e.attr("fill").value||"",s=e.hasAttr("stroke")&&e.attr("stroke").value||"",l=a+";s:"+s+";f:"+o,d=t.get(l);return d?void(!d.reused&&(d.reused=!0,!d.elem.hasAttr("id")&&d.elem.addAttr({name:"id",local:"id",prefix:"",value:"reuse-"+r++}),i.push(d.elem)),e=n(e,d.elem.attr("id").value)):void t.set(l,{elem:e,reused:!1})});const s=new o({elem:"defs",prefix:"",local:"defs",content:[],attrs:[]},e);e.content[0].spliceContent(0,0,s);for(let t of i){const e=t.style,r=t.class;delete t.style,delete t.class;const a=t.clone();t.style=e,t.class=r,a.removeAttr("transform"),s.spliceContent(0,0,a),t=n(t,a.attr("id").value),t.removeAttr("id")}return e}},17549:(e,t)=>{"use strict";t.type="perItem",t.active=!1,t.description="sorts element attributes (disabled by default)",t.params={order:["id","width","height","x","x1","x2","y","y1","y2","cx","cy","r","fill","stroke","marker","d","points"]},t.fn=function(e,t){var r=[],n={},o=t.order.length+1,i=t.xmlnsOrder||"front";e.elem&&(e.eachAttr(function(e){r.push(e)}),r.sort(function(e,r){if(e.prefix!=r.prefix){if("front"==i){if("xmlns"==e.prefix)return-1;if("xmlns"==r.prefix)return 1}return e.prefix<r.prefix?-1:1}for(var n=o,a=o,s=0;s<t.order.length;s++)e.name==t.order[s]?n=s:0===e.name.indexOf(t.order[s]+"-")&&(n=s+.5),r.name==t.order[s]?a=s:0===r.name.indexOf(t.order[s]+"-")&&(a=s+.5);return n==a?e.name<r.name?-1:1:n-a}),r.forEach(function(e){n[e.name]=e}),e.attrs=n)}},5681:(e,t)=>{"use strict";t.type="perItem",t.active=!0,t.description="Sorts children of <defs> to improve compression",t.fn=function(e){if(e.isElem("defs")){if(e.content){var t=e.content.reduce(function(e,t){return t.elem in e?e[t.elem]++:e[t.elem]=1,e},{});e.content.sort(function(e,r){var n=t[r.elem]-t[e.elem];if(0!=n)return n;var a=r.elem.length-e.elem.length;return 0==a?e.elem==r.elem?0:e.elem>r.elem?-1:1:a})}return!0}}},16974:(e,t,r)=>{function n(e,t,r,a){return t===r?a(null,!0):void e.lstat(t,function(o,i){return o&&"ENOENT"!==o.code?a(o):o||i.isDirectory()?n(e,p.join(t,".."),r,a):void a(null,!1)})}function a(e,t,r){l(e,{fs:t.fs},function(e,n){!e&&n&&t.own?o(n,t.uid,t.gid,r):r(e)})}var o=r(28729),i=r(15283),s=r(537),l=r(23607),d=r(35747),p=r(85622),c=r(12087),u="win32"===c.platform(),m=function(){},g=function(e){return e},h=u?function(e){return e.replace(/\\/g,"/").replace(/[:?<>|]/g,"_")}:g,f=function(e,t,r,n,a,o){var s=a||["."];return function(a){if(!s.length)return a();var l=s.shift(),d=p.join(r,l);t.call(e,d,function(t,c){return t?a(t):c.isDirectory()?void e.readdir(d,function(e,t){if(e)return a(e);o&&t.sort();for(var d=0;d<t.length;d++)n(p.join(r,l,t[d]))||s.push(p.join(l,t[d]));a(null,l,c)}):a(null,l,c)})}},y=function(e,t){return function(r){r.name=r.name.split("/").slice(t).join("/");var n=r.linkname;return n&&("link"===r.type||p.isAbsolute(n))&&(r.linkname=n.split("/").slice(t).join("/")),e(r)}};t.pack=function(e,t){e||(e="."),t||(t={});var r=t.fs||d,n=t.ignore||t.filter||m,a=t.map||m,o=t.mapStream||g,l=f(r,t.dereference?r.stat:r.lstat,e,n,t.entries,t.sort),c=!1!==t.strict,u="number"==typeof t.umask?~t.umask:~x(),b="number"==typeof t.dmode?t.dmode:0,S="number"==typeof t.fmode?t.fmode:0,v=t.pack||i.pack(),k=t.finish||m;t.strip&&(a=y(a,t.strip)),t.readable&&(b|=parseInt(555,8),S|=parseInt(444,8)),t.writable&&(b|=parseInt(333,8),S|=parseInt(222,8));var C=function(t,n){r.readlink(p.join(e,t),function(e,t){return e?v.destroy(e):void(n.linkname=h(t),v.entry(n,w))})},_=function(n,i,l){if(n)return v.destroy(n);if(!i)return!1!==t.finalize&&v.finalize(),k(v);if(l.isSocket())return w();var d={name:h(i),mode:(l.mode|(l.isDirectory()?b:S))&u,mtime:l.mtime,size:l.size,type:"file",uid:l.uid,gid:l.gid};if(l.isDirectory())return d.size=0,d.type="directory",d=a(d)||d,v.entry(d,w);if(l.isSymbolicLink())return d.size=0,d.type="symlink",d=a(d)||d,C(i,d);if(d=a(d)||d,!l.isFile())return c?v.destroy(new Error("unsupported type for "+i)):w();var m=v.entry(d,w);if(m){var g=o(r.createReadStream(p.join(e,i),{start:0,end:0<d.size?d.size-1:d.size}),d);g.on("error",function(e){m.destroy(e)}),s(g,m)}},w=function(e){return e?v.destroy(e):void l(_)};return w(),v};var b=function(e){return e.length?e[e.length-1]:null},S=function(){return process.getuid?process.getuid():-1},x=function(){return process.umask?process.umask():0};t.extract=function(e,t){e||(e="."),t||(t={});var r=t.fs||d,o=t.ignore||t.filter||m,l=t.map||m,c=t.mapStream||g,f=!1!==t.chown&&!Line truncated
*/function n(e,t){return e.renameElem("use"),e.removeAttr("d"),e.removeAttr("stroke"),e.removeAttr("fill"),e.addAttr({name:"xlink:href",local:"xlink:href",prefix:"none",value:"#"+t}),delete e.pathJS,e}function a(e,t){if(!e.isEmpty())for(let r of e.content)t(r),a(r,t)}var o=r(71921);t.type="full",t.active=!1,t.description="Finds <path> elements with the same d, fill, and stroke, and converts them to <use> elements referencing a single <path> def.",t.fn=function(e){const t=new Map;let r=0;const i=[];a(e,e=>{if(!e.isElem("path")||!e.hasAttr("d"))return;const a=e.attr("d").value,o=e.hasAttr("fill")&&e.attr("fill").value||"",s=e.hasAttr("stroke")&&e.attr("stroke").value||"",l=a+";s:"+s+";f:"+o,d=t.get(l);return d?void(!d.reused&&(d.reused=!0,!d.elem.hasAttr("id")&&d.elem.addAttr({name:"id",local:"id",prefix:"",value:"reuse-"+r++}),i.push(d.elem)),e=n(e,d.elem.attr("id").value)):void t.set(l,{elem:e,reused:!1})});const s=new o({elem:"defs",prefix:"",local:"defs",content:[],attrs:[]},e);e.content[0].spliceContent(0,0,s);for(let t of i){const e=t.style,r=t.class;delete t.style,delete t.class;const a=t.clone();t.style=e,t.class=r,a.removeAttr("transform"),s.spliceContent(0,0,a),t=n(t,a.attr("id").value),t.removeAttr("id")}return e}},17549:(e,t)=>{"use strict";t.type="perItem",t.active=!1,t.description="sorts element attributes (disabled by default)",t.params={order:["id","width","height","x","x1","x2","y","y1","y2","cx","cy","r","fill","stroke","marker","d","points"]},t.fn=function(e,t){var r=[],n={},o=t.order.length+1,i=t.xmlnsOrder||"front";e.elem&&(e.eachAttr(function(e){r.push(e)}),r.sort(function(e,r){if(e.prefix!=r.prefix){if("front"==i){if("xmlns"==e.prefix)return-1;if("xmlns"==r.prefix)return 1}return e.prefix<r.prefix?-1:1}for(var n=o,a=o,s=0;s<t.order.length;s++)e.name==t.order[s]?n=s:0===e.name.indexOf(t.order[s]+"-")&&(n=s+.5),r.name==t.order[s]?a=s:0===r.name.indexOf(t.order[s]+"-")&&(a=s+.5);return n==a?e.name<r.name?-1:1:n-a}),r.forEach(function(e){n[e.name]=e}),e.attrs=n)}},5681:(e,t)=>{"use strict";t.type="perItem",t.active=!0,t.description="Sorts children of <defs> to improve compression",t.fn=function(e){if(e.isElem("defs")){if(e.content){var t=e.content.reduce(function(e,t){return t.elem in e?e[t.elem]++:e[t.elem]=1,e},{});e.content.sort(function(e,r){var n=t[r.elem]-t[e.elem];if(0!=n)return n;var a=r.elem.length-e.elem.length;return 0==a?e.elem==r.elem?0:e.elem>r.elem?-1:1:a})}return!0}}},16974:(e,t,r)=>{function n(e,t,r,a){return t===r?a(null,!0):void e.lstat(t,function(o,i){return o&&"ENOENT"!==o.code?a(o):o||i.isDirectory()?n(e,p.join(t,".."),r,a):void a(null,!1)})}function a(e,t,r){l(e,{fs:t.fs},function(e,n){!e&&n&&t.own?o(n,t.uid,t.gid,r):r(e)})}var o=r(28729),i=r(15283),s=r(537),l=r(23607),d=r(35747),p=r(85622),c=r(12087),u="win32"===c.platform(),m=function(){},g=function(e){return e},h=u?function(e){return e.replace(/\\/g,"/").replace(/[:?<>|]/g,"_")}:g,f=function(e,t,r,n,a,o){var s=a||["."];return function(a){if(!s.length)return a();var l=s.shift(),d=p.join(r,l);t.call(e,d,function(t,c){return t?a(t):c.isDirectory()?void e.readdir(d,function(e,t){if(e)return a(e);o&&t.sort();for(var d=0;d<t.length;d++)n(p.join(r,l,t[d]))||s.push(p.join(l,t[d]));a(null,l,c)}):a(null,l,c)})}},y=function(e,t){return function(r){r.name=r.name.split("/").slice(t).join("/");var n=r.linkname;return n&&("link"===r.type||p.isAbsolute(n))&&(r.linkname=n.split("/").slice(t).join("/")),e(r)}};t.pack=function(e,t){e||(e="."),t||(t={});var r=t.fs||d,n=t.ignore||t.filter||m,a=t.map||m,o=t.mapStream||g,l=f(r,t.dereference?r.stat:r.lstat,e,n,t.entries,t.sort),c=!1!==t.strict,u="number"==typeof t.umask?~t.umask:~x(),b="number"==typeof t.dmode?t.dmode:0,S="number"==typeof t.fmode?t.fmode:0,v=t.pack||i.pack(),k=t.finish||m;t.strip&&(a=y(a,t.strip)),t.readable&&(b|=parseInt(555,8),S|=parseInt(444,8)),t.writable&&(b|=parseInt(333,8),S|=parseInt(222,8));var C=function(t,n){r.readlink(p.join(e,t),function(e,t){return e?v.destroy(e):void(n.linkname=h(t),v.entry(n,w))})},_=function(n,i,l){if(n)return v.destroy(n);if(!i)return!1!==t.finalize&&v.finalize(),k(v);if(l.isSocket())return w();var d={name:h(i),mode:(l.mode|(l.isDirectory()?b:S))&u,mtime:l.mtime,size:l.size,type:"file",uid:l.uid,gid:l.gid};if(l.isDirectory())return d.size=0,d.type="directory",d=a(d)||d,v.entry(d,w);if(l.isSymbolicLink())return d.size=0,d.type="symlink",d=a(d)||d,C(i,d);if(d=a(d)||d,!l.isFile())return c?v.destroy(new Error("unsupported type for "+i)):w();var m=v.entry(d,w);if(m){var g=o(r.createReadStream(p.join(e,i),{start:0,end:0<d.size?d.size-1:d.size}),d);g.on("error",function(e){m.destroy(e)}),s(g,m)}},w=function(e){return e?v.destroy(e):void l(_)};return w(),v};var b=function(e){return e.length?e[e.length-1]:null},S=function(){return process.getuid?process.getuid():-1},x=function(){return process.umask?process.umask():0};t.extract=function(e,t){e||(e="."),t||(t={});var r=t.fs||d,o=t.ignore||t.filter||m,l=t.map||m,c=t.mapStream||g,f=!1!==t.chown&&!Line truncated
+10 -4
View File
@@ -110,8 +110,10 @@
if (plugins.pagespeed.enabled) {
plugins.pagespeed.token = core.getInput("plugin_pagespeed_token") || ""
q[`pagespeed.detailed`] = bool(core.getInput(`plugin_pagespeed_detailed`))
q[`pagespeed.screenshot`] = bool(core.getInput(`plugin_pagespeed_screenshot`))
console.log(`Pagespeed token | ${plugins.pagespeed.token ? "provided" : "missing"}`)
console.log(`Pagespeed detailed | ${q["pagespeed.detailed"]}`)
console.log(`Pagespeed screenshot | ${q["pagespeed.screenshot"]}`)
}
//Languages
if (plugins.languages.enabled) {
@@ -156,16 +158,18 @@
}
//Topics
if (plugins.topics.enabled) {
for (const option of ["sort", "limit"])
for (const option of ["mode", "sort", "limit"])
q[`topics.${option}`] = core.getInput(`plugin_topics_${option}`) || null
console.log(`Topics mode | ${q["topics.mode"] || "(default)"}`)
console.log(`Topics sort mode | ${q["topics.sort"] || "(default)"}`)
console.log(`Topics limit | ${q["topics.limit"] || "(default)"}`)
}
//Projects
if (plugins.projects.enabled) {
for (const option of ["limit"])
for (const option of ["limit", "repositories"])
q[`projects.${option}`] = core.getInput(`plugin_projects_${option}`) || null
console.log(`Projects limit | ${q["projects.limit"] || "(default)"}`)
console.log(`Projects repositories | ${q["projects.repositories"] || "(none)"}`)
}
//Tweets
if (plugins.tweets.enabled) {
@@ -184,8 +188,10 @@
const die = bool(core.getInput("plugins_errors_fatal"))
console.log(`Plugin errors | ${die ? "die" : "warn"}`)
//Built query
q = {...q, base:false, ...base, ...config, repositories, template}
//Build query
const query = JSON.parse(core.getInput("query") || "{}")
console.log(`Query additional params | ${JSON.stringify(query)}`)
q = {...query, ...q, base:false, ...base, ...config, repositories, template}
//Render metrics
const rendered = await metrics({login:user, q, dflags}, {graphql, rest, plugins, conf, die})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "metrics",
"version": "2.9.0-beta",
"version": "2.10.0-beta",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "metrics",
"version": "2.9.0",
"version": "2.10.0-beta",
"description": "An image generator with 20+ metrics about your GitHub account such as activity, community, repositories, coding habits, website performances, music played, starred topics, etc. that you can put on your profile or elsewhere !",
"main": "index.mjs",
"scripts": {
+1 -1
View File
@@ -27,7 +27,7 @@
},
"habits":{ "//":"Habits plugin",
"enabled":false, "//":"Enable or disable coding habits metrics",
"from":100, "//":"Number of activity events to base habits on (up to 100)"
"from":200, "//":"Number of activity events to base habits on (up to 1000)"
},
"languages":{ "//":"Languages plugin",
"enabled":true, "//":"Enable or disable most used languages metrics"
+5 -1
View File
@@ -55,7 +55,8 @@
"languages.ignored":"",
"languages.skipped":"",
"pagespeed.detailed":false,
"habits.from":100,
"pagespeed.screenshot":false,
"habits.from":200,
"habits.days":14,
"habits.facts":true,
"habits.charts":false,
@@ -65,6 +66,8 @@
"posts.source":"dev.to",
"isocalendar.duration":"half-year",
"projects.limit":4,
"projects.repositories":"",
"topics.mode":"starred",
"topics.sort":"stars",
"topics.limit":12,
"tweets.limit":2,
@@ -78,6 +81,7 @@
descriptions:{
classic:"Classic template",
terminal:"Terminal template",
repository:"(hidden)",
},
},
generated:{
+16 -1
View File
@@ -28,7 +28,7 @@
<div class="step">
<h2>2. Select a template</h2>
<div class="templates">
<label v-for="template in templates.list" :key="template">
<label v-for="template in templates.list" :key="template" v-show="templates.descriptions[template] !== '(hidden)'">
<input type="radio" v-model="templates.selected" :value="template" @change="load" :disabled="generated.pending">
{{ templates.descriptions[template] || template }}
</label>
@@ -78,6 +78,10 @@
Detailed PageSpeed report
<input type="checkbox" v-model="plugins.options['pagespeed.detailed']" @change="load">
</label>
<label>
Include a website screenshot
<input type="checkbox" v-model="plugins.options['pagespeed.screenshot']" @change="load">
</label>
</div>
<div class="options-group" v-if="plugins.enabled.languages">
<h4>{{ plugins.descriptions.languages }}</h4>
@@ -134,6 +138,13 @@
</div>
<div class="options-group" v-if="plugins.enabled.topics">
<h4>{{ plugins.descriptions.topics }}</h4>
<label>
Topics display mode
<select v-model="plugins.options['topics.mode']" @change="load">
<option value="starred">Starred topics</option>
<option value="mastered">Known and mastered technologies</option>
</select>
</label>
<label>
Topics sorting
<select v-model="plugins.options['topics.sort']">
@@ -154,6 +165,10 @@
Number of projects to display
<input type="number" v-model="plugins.options['projects.limit']" min="1" max="100" @change="load">
</label>
<label>
Repositories projects to display (comma separated)
<input type="text" v-model="plugins.options['projects.repositories']" @change="load">
</label>
</div>
</div>
</template>
+32 -21
View File
@@ -27,8 +27,9 @@
const s = (value, end = "") => value > 1 ? {y:"ies", "":"s"}[end] : end
if ((!(template in Templates))||(!(template in conf.templates))||((conf.settings.templates.enabled.length)&&(!conf.settings.templates.enabled.includes(template))))
throw new Error("unsupported template")
const {query, image, style, fonts} = conf.templates[template]
const data = {base:{}, config:{}}
const {image, style, fonts} = conf.templates[template]
const queries = conf.queries
const data = {base:{}, config:{}, errors:[], plugins:{}, computed:{}}
//Base parts
{
@@ -44,29 +45,39 @@
else {
//Query data from GitHub API
console.debug(`metrics/compute/${login} > graphql query`)
Object.assign(data, await graphql(query
.replace(/[$]login/, `"${login}"`)
.replace(/[$]repositories/, `${repositories}`)
.replace(/[$]calendar.to/, `"${(new Date()).toISOString()}"`)
.replace(/[$]calendar.from/, `"${(new Date(Date.now()-14*24*60*60*1000)).toISOString()}"`)
))
Object.assign(data, await graphql(queries.common({login, "calendar.from":new Date(Date.now()-14*24*60*60*1000).toISOString(), "calendar.to":(new Date()).toISOString()})))
//Query repositories from GitHub API
{
//Iterate through repositories
let cursor = null
let pushed = 0
do {
console.debug(`metrics/compute/${login} > retrieving repositories after ${cursor}`)
const {user:{repositories:{edges, nodes}}} = await graphql(queries.repositories({login, after:cursor ? `after: "${cursor}"` : "", repositories:Math.min(repositories, 100)}))
cursor = edges?.[edges?.length-1]?.cursor
data.user.repositories.nodes.push(...nodes)
pushed = nodes.length
} while ((pushed)&&(cursor)&&(data.user.repositories.nodes.length < repositories))
//Limit repositories
console.debug(`metrics/compute/${login} > keeping only ${repositories} repositories`)
data.user.repositories.nodes.splice(repositories)
console.debug(`metrics/compute/${login} > loaded ${data.user.repositories.nodes.length} repositories`)
}
//Compute metrics
console.debug(`metrics/compute/${login} > compute`)
const computer = Templates[template].default || Templates[template]
await computer({login, q, dflags}, {conf, data, rest, graphql, plugins}, {s, pending, imports:{plugins:Plugins, url, imgb64, axios, puppeteer, run, fs, os, paths, util, format, bytes, shuffle, htmlescape, urlexpand}})
await computer({login, q, dflags}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports:{plugins:Plugins, url, imgb64, axios, puppeteer, run, fs, os, paths, util, format, bytes, shuffle, htmlescape, urlexpand}})
const promised = await Promise.all(pending)
//Check plugins errors
{
const errors = promised.filter(({result = null}) => result?.error)
if (die) {
if (errors.length)
throw new Error(`${errors.length} error${s(errors.length)} found...`)
}
else {
console.warn(`${errors.length} error${s(errors.length)} found, ignoring...`)
console.warn(util.inspect(errors, {depth:Infinity, maxStringLength:256}))
const errors = [...promised.filter(({result = null}) => result?.error), ...data.errors]
if (errors.length) {
console.warn(`metrics/compute/${login} > ${errors.length} errors !`)
if (die)
throw new Error(`An error occured during rendering, dying`)
else
console.warn(util.inspect(errors, {depth:Infinity, maxStringLength:256}))
}
}
}
@@ -159,7 +170,7 @@
/** Placeholder generator */
function placeholder({data, conf, q}) {
//Proxifier
const proxify = (target) => typeof target === "object" ? new Proxy(target, {
const proxify = (target) => (typeof target === "object")&&(target) ? new Proxy(target, {
get(target, property) {
//Primitive conversion
if (property === Symbol.toPrimitive)
@@ -196,11 +207,11 @@
[key, proxify({
posts:{source:"########", list:new Array("posts.limit" in q ? Math.max(Number(q["posts.limit"])||0, 0) : 2).fill({title:"###### ###### ####### ######", date:"####"})},
music:{provider:"########", tracks:new Array("music.limit" in q ? Math.max(Number(q["music.limit"])||0, 0) : 4).fill({name:"##########", artist:"######", artwork:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg=="})},
pagespeed:{detailed:!!q["pagespeed.detailed"], scores:["Performance", "Accessibility", "Best Practices", "SEO"].map(title => ({title, score:NaN}))},
pagespeed:{detailed:!!q["pagespeed.detailed"], screenshot:!!q["pagespeed.screenshot"] ? "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==" : null, scores:["Performance", "Accessibility", "Best Practices", "SEO"].map(title => ({title, score:NaN}))},
followup:{issues:{count:0}, pr:{count:0}},
habits:{facts:!!(q["habits.facts"] ?? 1), charts:!!q["habits.charts"], indents:{style:`########`}, commits:{day:"####"}, linguist:{ordered:[]}},
languages:{favorites:new Array(7).fill(null).map((_, x) => ({x, name:"######", color:"#ebedf0", value:1/(x+1)}))},
topics:{list:[...new Array("topics.limit" in q ? Math.max(Number(q["topics.limit"])||0, 0) : 12).fill(null).map(() => ({name:"######", description:"", icon:null})), {name:`And ## more...`, description:"", icon:null}]},
topics:{mode:"topics.mode" in q ? q["topics.mode"] : "starred", list:[...new Array("topics.limit" in q ? Math.max(Number(q["topics.limit"])||0, 0) : 12).fill(null).map(() => ({name:"######", description:"", icon:null})), {name:`And ## more...`, description:"", icon:null}]},
projects:{list:[...new Array("projects.limit" in q ? Math.max(Number(q["projects.limit"])||0, 0) : 4).fill(null).map(() => ({name:"########", updated:"########", progress:{enabled:true, todo:"##", doing:"##", done:"##", total:"##"}}))]},
tweets:{profile:{username:"########", verified:false}, list:[...new Array("tweets.limit" in q ? Math.max(Number(q["tweets.limit"])||0, 0) : 2).fill(null).map(() => ({text:"###### ###### ####### ######".repeat(4), created_at:Date.now()}))]},
}[key]??{})]
+6 -24
View File
@@ -1,5 +1,5 @@
//Setup
export default async function ({login, graphql, q}, {enabled = false} = {}) {
export default async function ({login, graphql, q, queries}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -7,40 +7,22 @@
return null
//Retrieve gists from graphql api
console.debug(`metrics/compute/${login}/plugins > gists > querying api`)
const {user:{gists}} = await graphql(`
query Gists {
user(login: "${login}") {
gists(last: 100) {
totalCount
nodes {
stargazerCount
isFork
forks {
totalCount
}
comments {
totalCount
}
}
}
}
}
`
)
const {user:{gists}} = await graphql(queries.gists({login}))
//Iterate through gists
console.debug(`metrics/compute/${login}/plugins > gists > processing ${gists.nodes.length} gists`)
let stargazers = 0, forks = 0, comments = 0
let stargazers = 0, forks = 0, comments = 0, files = 0
for (const gist of gists.nodes) {
//Skip forks
if (gist.isFork)
continue
//Compute stars, forks and comments
//Compute stars, forks, comments and files count
stargazers += gist.stargazerCount
forks += gist.forks.totalCount
comments += gist.comments.totalCount
files += gist.files.length
}
//Results
return {totalCount:gists.totalCount, stargazers, forks, comments}
return {totalCount:gists.totalCount, stargazers, forks, files, comments}
}
//Handle errors
catch (error) {
+2 -19
View File
@@ -1,5 +1,5 @@
//Setup
export default async function ({login, graphql, q}, {enabled = false} = {}) {
export default async function ({login, graphql, q, queries}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
@@ -24,24 +24,7 @@
const calendar = {}
for (const [name, from, to] of [["padding", padding, start], ["weeks", start, now]]) {
console.debug(`metrics/compute/${login}/plugins > isocalendar > loading ${name} from "${from.toISOString()}" to "${to.toISOString()}"`)
const {user:{calendar:{contributionCalendar:{weeks}}}} = await graphql(`
query Calendar {
user(login: "${login}") {
calendar:contributionsCollection(from: "${from.toISOString()}", to: "${to.toISOString()}") {
contributionCalendar {
weeks {
contributionDays {
contributionCount
color
date
}
}
}
}
}
}
`
)
const {user:{calendar:{contributionCalendar:{weeks}}}} = await graphql(queries.calendar({login, from:from.toISOString(), to:to.toISOString()}))
calendar[name] = weeks
}
//Apply padding
+1 -1
View File
@@ -171,7 +171,7 @@
//Limit tracklist
if (limit > 0) {
console.debug(`metrics/compute/${login}/plugins > music > keeping only ${limit} tracks`)
tracks = tracks.slice(0, limit)
tracks.splice(limit)
}
//Convert artworks to base64
console.debug(`metrics/compute/${login}/plugins > music > loading artworks`)
+13 -7
View File
@@ -6,7 +6,7 @@
if ((!enabled)||(!q.pagespeed)||(!data.user.websiteUrl))
return null
//Parameters override
let {"pagespeed.detailed":detailed = false} = q
let {"pagespeed.detailed":detailed = false, "pagespeed.screenshot":screenshot = false} = q
//Duration in days
detailed = !!detailed
//Format url if needed
@@ -18,12 +18,18 @@
console.debug(`metrics/compute/${login}/plugins > pagespeed > querying api for ${url}`)
const scores = new Map()
await Promise.all(["performance", "accessibility", "best-practices", "seo"].map(async category => {
console.debug(`metrics/compute/${login}/plugins > pagespeed > performing audit ${category}`)
const request = await imports.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=${category}&url=${url}&key=${token}`)
console.debug(request.data)
const {score, title} = request.data.lighthouseResult.categories[category]
scores.set(category, {score, title})
console.debug(`metrics/compute/${login}/plugins > pagespeed > performed audit ${category} (status code ${request.status})`)
//Perform audit
console.debug(`metrics/compute/${login}/plugins > pagespeed > performing audit ${category}`)
const request = await imports.axios.get(`https://www.googleapis.com/pagespeedonline/v5/runPagespeed?category=${category}&url=${url}&key=${token}`)
console.debug(request.data)
const {score, title} = request.data.lighthouseResult.categories[category]
scores.set(category, {score, title})
console.debug(`metrics/compute/${login}/plugins > pagespeed > performed audit ${category} (status code ${request.status})`)
//Store screenshot
if ((screenshot)&&(category === "performance")) {
result.screenshot = request.data.lighthouseResult.audits["final-screenshot"].details.data
console.debug(`metrics/compute/${login}/plugins > pagespeed > performed audit ${category} (status code ${request.status})`)
}
}))
result.scores = [scores.get("performance"), scores.get("accessibility"), scores.get("best-practices"), scores.get("seo")]
//Detailed metrics
+1 -1
View File
@@ -29,7 +29,7 @@
//Limit tracklist
if (limit > 0) {
console.debug(`metrics/compute/${login}/plugins > posts > keeping only ${limit} posts`)
posts = posts.slice(0, limit)
posts.splice(limit)
}
//Results
return {source, list:posts}
+24 -25
View File
@@ -1,38 +1,34 @@
//Setup
export default async function ({login, graphql, q}, {enabled = false} = {}) {
export default async function ({login, graphql, q, queries}, {enabled = false} = {}) {
//Plugin execution
try {
//Check if plugin is enabled and requirements are met
if ((!enabled)||(!q.projects))
return null
//Parameters override
let {"projects.limit":limit = 4} = q
let {"projects.limit":limit = 4, "projects.repositories":repositories = ""} = q
//Repositories projects
repositories = repositories?.split(",").map(repository => repository.trim()).filter(repository => /[-\w]+[/][-\w]+[/]projects[/]\d+/.test(repository)) ?? []
//Limit
limit = Math.max(1, Math.min(100, Number(limit)))
//Retrieve contribution calendar from graphql api
limit = Math.max(repositories.length, Math.min(100, Number(limit)))
//Retrieve user owned projects from graphql api
console.debug(`metrics/compute/${login}/plugins > projects > querying api`)
const {user:{projects}} = await graphql(`
query Projects {
user(login: "${login}") {
projects(last: ${limit}, states: OPEN, orderBy: {field: UPDATED_AT, direction: DESC}) {
totalCount
nodes {
name
updatedAt
progress {
doneCount
inProgressCount
todoCount
enabled
}
}
}
}
}
`
)
const {user:{projects}} = await graphql(queries.projects({login, limit}))
//Retrieve repositories projects from graphql api
for (const identifier of repositories) {
//Querying repository project
console.debug(`metrics/compute/${login}/plugins > projects > querying api for ${identifier}`)
const {user, repository, id} = identifier.match(/(?<user>[-\w]+)[/](?<repository>[-\w]+)[/]projects[/](?<id>\d+)/)?.groups
const {user:{repository:{project}}} = await graphql(queries["projects.repository"]({user, repository, id}))
//Adding it to projects list
console.debug(`metrics/compute/${login}/plugins > projects > registering ${identifier}`)
project.name = `${project.name} (${user}/${repository})`
projects.nodes.unshift(project)
projects.totalCount++
}
//Iterate through projects and format them
console.debug(`metrics/compute/${login}/plugins > posts > processing ${projects.nodes.length} projects`)
console.debug(`metrics/compute/${login}/plugins > projects > processing ${projects.nodes.length} projects`)
const list = []
for (const project of projects.nodes) {
//Format date
@@ -49,6 +45,9 @@
//Append
list.push({name:project.name, updated, progress:{enabled, todo, doing, done, total:todo+doing+done}})
}
//Limit
console.debug(`metrics/compute/${login}/plugins > projects > keeping only ${limit} projects`)
list.splice(limit)
//Results
return {list, totalCount:projects.totalCount}
}
+18 -7
View File
@@ -6,13 +6,15 @@
if ((!enabled)||(!q.topics))
return null
//Parameters override
let {"topics.sort":sort = "stars", "topics.limit":limit = 15} = q
let {"topics.sort":sort = "stars", "topics.mode":mode = "starred", "topics.limit":limit = (mode === "mastered" ? 0 : 15)} = q
//Shuffle
const shuffle = (sort === "random")
//Sort method
sort = {starred:"created", activity:"updated", stars:"stars", random:"created"}[sort] ?? "starred"
//Limit
limit = Math.max(1, Math.min(20, Number(limit)))
limit = Math.max(0, Math.min(20, Number(limit)))
//Mode
mode = ["starred", "mastered"].includes(mode) ? mode : "starred"
//Start puppeteer and navigate to topics
console.debug(`metrics/compute/${login}/plugins > topics > searching starred topics`)
let topics = []
@@ -49,11 +51,10 @@
console.debug(`metrics/compute/${login}/plugins > topics > shuffling topics`)
topics = imports.shuffle(topics)
}
//Limit topics
if (limit > 0) {
//Limit topics (starred mode)
if ((mode === "starred")&&(limit > 0)) {
console.debug(`metrics/compute/${login}/plugins > topics > keeping only ${limit} topics`)
const removed = topics.slice(limit)
topics = topics.slice(0, limit)
const removed = topics.splice(limit)
topics.push({name:`And ${removed.length} more...`, description:removed.map(({name}) => name).join(", "), icon:null})
}
//Convert icons to base64
@@ -66,8 +67,18 @@
//Escape HTML description
topic.description = imports.htmlescape(topic.description)
}
//Filter topics with icon (mastered mode)
if (mode === "mastered") {
console.debug(`metrics/compute/${login}/plugins > topics > filtering topics with icon`)
topics = topics.filter(({icon}) => icon)
}
//Limit topics (mastered mode)
if ((mode === "mastered")&&(limit > 0)) {
console.debug(`metrics/compute/${login}/plugins > topics > keeping only ${limit} topics`)
topics.splice(limit)
}
//Results
return {list:topics}
return {mode, list:topics}
}
//Handle errors
catch (error) {
+15
View File
@@ -0,0 +1,15 @@
query Calendar {
user(login: "$login") {
calendar:contributionsCollection(from: "$from", to: "$to") {
contributionCalendar {
weeks {
contributionDays {
contributionCount
color
date
}
}
}
}
}
}
@@ -1,5 +1,5 @@
query Metrics {
user(login: $login) {
user(login: "$login") {
databaseId
name
login
@@ -11,45 +11,11 @@ query Metrics {
gists {
totalCount
}
repositories(last: $repositories, isFork: false, ownerAffiliations: OWNER) {
repositories(last: 0, isFork: false, ownerAffiliations: OWNER) {
totalCount
totalDiskUsage
nodes {
name
watchers {
totalCount
}
stargazers {
totalCount
}
languages(first: 4) {
edges {
size
node {
color
name
}
}
}
issues_open: issues(states: OPEN) {
totalCount
}
issues_closed: issues(states: CLOSED) {
totalCount
}
pr_open: pullRequests(states: OPEN) {
totalCount
}
pr_merged: pullRequests(states: MERGED) {
totalCount
}
releases {
totalCount
}
forkCount
licenseInfo {
spdxId
}
}
}
packages {
@@ -75,7 +41,7 @@ query Metrics {
totalPullRequestContributions
totalPullRequestReviewContributions
}
calendar:contributionsCollection(from: $calendar.from, to: $calendar.to) {
calendar:contributionsCollection(from: "$calendar.from", to: "$calendar.to") {
contributionCalendar {
weeks {
contributionDays {
+20
View File
@@ -0,0 +1,20 @@
query Gists {
user(login: "$login") {
gists(last: 100) {
totalCount
nodes {
stargazerCount
isFork
forks {
totalCount
}
files {
name
}
comments {
totalCount
}
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
query Projects {
user(login: "$login") {
projects(last: $limit, states: OPEN, orderBy: {field: UPDATED_AT, direction: DESC}) {
totalCount
nodes {
name
updatedAt
progress {
doneCount
inProgressCount
todoCount
enabled
}
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
query Projects {
user(login: "$user") {
repository(name: "$repository") {
project(number: $id) {
name
updatedAt
progress {
doneCount
inProgressCount
todoCount
enabled
}
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
query Metrics {
user(login: "$login") {
repositories($after first: $repositories, isFork: false, ownerAffiliations: OWNER, orderBy: {field: UPDATED_AT, direction: DESC}) {
edges {
cursor
}
nodes {
name
watchers {
totalCount
}
stargazers {
totalCount
}
languages(first: 8) {
edges {
size
node {
color
name
}
}
}
issues_open: issues(states: OPEN) {
totalCount
}
issues_closed: issues(states: CLOSED) {
totalCount
}
pr_open: pullRequests(states: OPEN) {
totalCount
}
pr_merged: pullRequests(states: MERGED) {
totalCount
}
releases {
totalCount
}
forkCount
licenseInfo {
spdxId
}
}
}
}
}
+43
View File
@@ -0,0 +1,43 @@
query Metrics {
user(login: "$login") {
repository(name: "$repo") {
name
createdAt
diskUsage
watchers {
totalCount
}
stargazers {
totalCount
}
languages(first: 8) {
edges {
size
node {
color
name
}
}
}
issues_open: issues(states: OPEN) {
totalCount
}
issues_closed: issues(states: CLOSED) {
totalCount
}
pr_open: pullRequests(states: OPEN) {
totalCount
}
pr_merged: pullRequests(states: MERGED) {
totalCount
}
releases {
totalCount
}
forkCount
licenseInfo {
spdxId
}
}
}
}
+40 -6
View File
@@ -10,8 +10,10 @@
const logger = log ? console.debug : () => null
logger(`metrics/setup > setup`)
const templates = "src/templates"
const queries = "src/queries"
const conf = {
templates:{},
queries:{},
settings:{},
statics:path.resolve("src/html"),
node_modules:path.resolve("node_modules"),
@@ -52,22 +54,21 @@
continue
logger(`metrics/setup > load template [${name}]`)
const files = [
`${templates}/${name}/query.graphql`,
`${templates}/${name}/image.svg`,
`${templates}/${name}/style.css`,
`${templates}/${name}/fonts.css`,
]
const [query, image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
conf.templates[name] = {query, image, style, fonts}
].map(file => fs.existsSync(path.resolve(file)) ? file : file.replace(`${templates}/${name}/`, `${templates}/classic/`)).map(file => path.resolve(file))
const [image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(file)}`))
conf.templates[name] = {image, style, fonts}
logger(`metrics/setup > load template [${name}] > success`)
//Debug
if (conf.settings.debug) {
Object.defineProperty(conf.templates, name, {
get() {
logger(`metrics/setup > reload template [${name}]`)
const [query, image, style, fonts] = files.map(file => `${fs.readFileSync(path.resolve(file))}`)
const [image, style, fonts] = files.map(file => `${fs.readFileSync(file)}`)
logger(`metrics/setup > reload template [${name}] > success`)
return {query, image, style, fonts}
return {image, style, fonts}
}
})
}
@@ -78,6 +79,39 @@
conf.templates = JSON.parse(Buffer.from(`<#assets>`, "base64").toString("utf8"))
}
//Load queries
if (fs.existsSync(path.resolve(queries))) {
for (const query of await fs.promises.readdir(queries)) {
//Cache queries
const name = query.replace(/[.]graphql$/, "")
logger(`metrics/setup > load query [${name}]`)
conf.queries[`_${name}`] = `${await fs.promises.readFile(path.resolve(`${queries}/${query}`))}`
logger(`metrics/setup > load query [${name}] > success`)
//Debug
if (conf.settings.debug) {
Object.defineProperty(conf.queries, `_${name}`, {
get() {
logger(`metrics/setup > reload query [${name}]`)
const raw = `${fs.readFileSync(path.resolve(`${queries}/${query}`))}`
logger(`metrics/setup > reload query [${name}] > success`)
return raw
}
})
}
}
}
else {
logger(`metrics/setup > load queries from build`)
conf.queries = JSON.parse(Buffer.from(`<#queries>`, "base64").toString("utf8"))
}
//Create queries formatters
Object.keys(conf.queries).map(name => conf.queries[name.substring(1)] = (vars = {}) => {
let query = conf.queries[name]
for (const [key, value] of Object.entries(vars))
query = query.replace(new RegExp(`[$]${key}`, "g"), value)
return query
})
//Conf
logger(`metrics/setup > setup > success`)
return conf
+34 -11
View File
@@ -5,7 +5,7 @@
+ (!!base.repositories)*108
+ ((!!base.repositories)*((!!plugins.traffic)||(!!plugins.lines)))*16
+ (!!plugins.followup)*68
+ (!!plugins.pagespeed)*126 + (plugins.pagespeed?.detailed ?? 0)*6*20
+ (!!plugins.pagespeed)*126 + (plugins.pagespeed?.detailed ?? 0)*6*20 + (!!plugins.pagespeed?.screenshot)*330
+ (!!plugins.habits)*28 + (!!plugins.habits?.facts)*58 + (!!plugins.habits?.charts)*226
+ (!!plugins.languages)*96
+ (!!plugins.music)*64 + (plugins.music?.tracks?.length ? 14+Math.max(0, plugins.music.tracks.length-1)*36 : 0)
@@ -380,11 +380,19 @@
<% } else { %>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>
<%= plugins.gists.stargazers %> Stargazer<%= s(plugins.gists.stargazers) %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4 1.75C4 .784 4.784 0 5.75 0h5.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0114.25 15h-9a.75.75 0 010-1.5h9a.25.25 0 00.25-.25V6h-2.75A1.75 1.75 0 0110 4.25V1.5H5.75a.25.25 0 00-.25.25v2.5a.75.75 0 01-1.5 0v-2.5zm7.5-.188V4.25c0 .138.112.25.25.25h2.688a.252.252 0 00-.011-.013l-2.914-2.914a.272.272 0 00-.013-.011zM5.72 6.72a.75.75 0 000 1.06l1.47 1.47-1.47 1.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM3.28 7.78a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 001.06-1.06L1.81 9.25l1.47-1.47z"></path></svg>
<%= plugins.gists.files %> File<%= s(plugins.gists.files) %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.75 2.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 01.75.75v2.19l2.72-2.72a.75.75 0 01.53-.22h4.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25H2.75zM1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0113.25 12H9.06l-2.573 2.573A1.457 1.457 0 014 13.543V12H2.75A1.75 1.75 0 011 10.25v-7.5z"></path></svg>
<%= plugins.gists.comments %> Comment<%= s(plugins.gists.comments) %>
</div>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>
<%= plugins.gists.stargazers %> Stargazer<%= s(plugins.gists.stargazers) %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>
<%= plugins.gists.forks %> Fork<%= s(plugins.gists.forks) %>
@@ -497,6 +505,13 @@
</section>
</div>
<% } %>
<% if (plugins.pagespeed.screenshot) { %>
<div class="row">
<section>
<img class="screenshot" src="<%= plugins.pagespeed.screenshot %>" width="452" height="315"/>
</section>
</div>
<% } %>
<% } %>
<% } %>
@@ -585,7 +600,7 @@
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M14.184 1.143a1.75 1.75 0 00-2.502-.57L.912 7.916a1.75 1.75 0 00-.53 2.32l.447.775a1.75 1.75 0 002.275.702l11.745-5.656a1.75 1.75 0 00.757-2.451l-1.422-2.464zm-1.657.669a.25.25 0 01.358.081l1.422 2.464a.25.25 0 01-.108.35l-2.016.97-1.505-2.605 1.85-1.26zM9.436 3.92l1.391 2.41-5.42 2.61-.942-1.63 4.97-3.39zM3.222 8.157l-1.466 1a.25.25 0 00-.075.33l.447.775a.25.25 0 00.325.1l1.598-.769-.83-1.436zm6.253 2.306a.75.75 0 00-.944-.252l-1.809.87a.75.75 0 00-.293.253L4.38 14.326a.75.75 0 101.238.848l1.881-2.75v2.826a.75.75 0 001.5 0v-2.826l1.881 2.75a.75.75 0 001.238-.848l-2.644-3.863z"></path></svg>
Starred topics
<%= {starred:"Starred topics", mastered:"Mastered technologies and topics"}[plugins.topics.mode] %>
</h2>
<div class="row">
<% if (plugins.topics.error) { %>
@@ -596,13 +611,21 @@
</div>
</section>
<% } else { %>
<section>
<div class="topics fill-width">
<% for (const {name, description} of plugins.topics.list) { %>
<div class="label" title="<%= description %>"><%= name.toLocaleLowerCase() %></div>
<% } %>
</div>
</section>
<section>
<div class="topics fill-width">
<% if (plugins.topics.mode === "starred") { %>
<% for (const {name, description} of plugins.topics.list) { %>
<div class="label" title="<%= description %>"><%= name.toLocaleLowerCase() %></div>
<% } %>
<% } else if (plugins.topics.mode === "mastered") { %>
<% for (const {name, icon} of plugins.topics.list) { %>
<% if (icon) { %>
<img src="data:image/png;base64,<%= icon %>" width="24" height="24" alt="<%= name %>" title="<%= name %>"/>
<% } %>
<% } %>
<% } %>
</div>
</section>
<% } %>
</div>
</section>

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 75 KiB

+12
View File
@@ -226,6 +226,13 @@
fill: #e53935;
}
.screenshot {
width: 452px;
height: 315px;
margin: 8px 14px 4px;
border-radius: 5px;
}
/* Music plugin */
.tracklist {
display: flex;
@@ -284,6 +291,11 @@
flex-wrap: wrap;
}
.topics img {
border-radius: 5px;
margin: 4px;
}
/* Tweets */
.tweet {
font-size: 13px;
+1 -1
View File
@@ -2,7 +2,7 @@
import common from "./../common.mjs"
/** Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) {
//Common
await common(...arguments)
}
+2 -3
View File
@@ -1,10 +1,9 @@
/** Template common processor */
export default async function ({login, q, dflags}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
export default async function ({login, q, dflags}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) {
//Init
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, releases:0}}
const avatar = imports.imgb64(data.user.avatarUrl)
data.plugins = {}
console.debug(`metrics/compute/${login} > formatting common metrics`)
//Timezone config
@@ -24,7 +23,7 @@
pending.push((async () => {
try {
console.debug(`metrics/compute/${login}/plugins > ${name} > started`)
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql}, plugins[name])
data.plugins[name] = await imports.plugins[name]({login, q, imports, data, computed, rest, graphql, queries}, plugins[name])
console.debug(`metrics/compute/${login}/plugins > ${name} > completed (${data.plugins[name] !== null ? "success" : "skipped"})`)
}
catch (error) {
+2
View File
@@ -2,10 +2,12 @@
//Imports
import classic from "./classic/template.mjs"
import repository from "./repository/template.mjs"
import terminal from "./terminal/template.mjs"
//Exports
export default {
classic,
repository,
terminal,
}
+220
View File
@@ -0,0 +1,220 @@
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="<%= 0
+ (!!base.header)*42
+ (!!plugins.traffic)*18
+ (!!plugins.followup)*68
+ (!!base.metadata)*28
+ (!!plugins.projects)*22 + (plugins.projects?.list?.length ?? 0)*60 + (!!plugins.projects?.error)*22
%>">
<defs><style><%= fonts %></style></defs>
<style>
<%= style %>
</style>
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<% if (errors.length) { %>
<section>
<div class="row">
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= errors.map(({error}) => error.message).join(", ") %>
</div>
</div>
</section>
<% } else { %>
<% if (base.header) { %>
<section>
<div class="row">
<section>
<div class="field <%= computed.cakeday ? 'cakeday' : '' %>">
<% if (computed.cakeday) { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.75 1.5a1.25 1.25 0 100 2.5h2.309c-.233-.818-.542-1.401-.878-1.793-.43-.502-.915-.707-1.431-.707zM2 2.75c0 .45.108.875.3 1.25h-.55A1.75 1.75 0 000 5.75v2c0 .698.409 1.3 1 1.582v4.918c0 .966.784 1.75 1.75 1.75h10.5A1.75 1.75 0 0015 14.25V9.332c.591-.281 1-.884 1-1.582v-2A1.75 1.75 0 0014.25 4h-.55a2.75 2.75 0 00-2.45-4c-.984 0-1.874.42-2.57 1.23A5.086 5.086 0 008 2.274a5.086 5.086 0 00-.68-1.042C6.623.42 5.733 0 4.75 0A2.75 2.75 0 002 2.75zM8.941 4h2.309a1.25 1.25 0 100-2.5c-.516 0-1 .205-1.43.707-.337.392-.646.975-.879 1.793zm-1.84 1.5H1.75a.25.25 0 00-.25.25v2c0 .138.112.25.25.25h5.5V5.5h-.149zm1.649 0V8h5.5a.25.25 0 00.25-.25v-2a.25.25 0 00-.25-.25h-5.5zm0 4h4.75v4.75a.25.25 0 01-.25.25h-4.5v-5zm-1.5 0v5h-4.5a.25.25 0 01-.25-.25V9.5h4.75z"></path></svg>
Created <%= computed.registration %>
<% } else { %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"></path></svg>
Created <%= computed.registration %>
<% } %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" d="M2.5 3.5c0-.133.058-.318.282-.55.227-.237.592-.484 1.1-.708C4.899 1.795 6.354 1.5 8 1.5c1.647 0 3.102.295 4.117.742.51.224.874.47 1.101.707.224.233.282.418.282.551 0 .133-.058.318-.282.55-.227.237-.592.484-1.1.708C11.101 5.205 9.646 5.5 8 5.5c-1.647 0-3.102-.295-4.117-.742-.51-.224-.874-.47-1.101-.707-.224-.233-.282-.418-.282-.551zM1 3.5c0-.626.292-1.165.7-1.59.406-.422.956-.767 1.579-1.041C4.525.32 6.195 0 8 0c1.805 0 3.475.32 4.722.869.622.274 1.172.62 1.578 1.04.408.426.7.965.7 1.591v9c0 .626-.292 1.165-.7 1.59-.406.422-.956.767-1.579 1.041C11.476 15.68 9.806 16 8 16c-1.805 0-3.475-.32-4.721-.869-.623-.274-1.173-.62-1.579-1.04-.408-.426-.7-.965-.7-1.591v-9zM2.5 8V5.724c.241.15.503.286.779.407C4.525 6.68 6.195 7 8 7c1.805 0 3.475-.32 4.722-.869.275-.121.537-.257.778-.407V8c0 .133-.058.318-.282.55-.227.237-.592.484-1.1.708C11.101 9.705 9.646 10 8 10c-1.647 0-3.102-.295-4.117-.742-.51-.224-.874-.47-1.101-.707C2.558 8.318 2.5 8.133 2.5 8zm0 2.225V12.5c0 .133.058.318.282.55.227.237.592.484 1.1.708 1.016.447 2.471.742 4.118.742 1.647 0 3.102-.295 4.117-.742.51-.224.874-.47 1.101-.707.224-.233.282-.418.282-.551v-2.275c-.241.15-.503.285-.778.406-1.247.549-2.917.869-4.722.869-1.805 0-3.475-.32-4.721-.869a6.236 6.236 0 01-.779-.406z"></path></svg>
<%= computed.diskUsage %> used
</div>
<% if (plugins.traffic) { %>
<div class="field <%= plugins.traffic.error ? 'error' : '' %>">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 1.75A.75.75 0 01.75 1h4.253c1.227 0 2.317.59 3 1.501A3.744 3.744 0 0111.006 1h4.245a.75.75 0 01.75.75v10.5a.75.75 0 01-.75.75h-4.507a2.25 2.25 0 00-1.591.659l-.622.621a.75.75 0 01-1.06 0l-.622-.621A2.25 2.25 0 005.258 13H.75a.75.75 0 01-.75-.75V1.75zm8.755 3a2.25 2.25 0 012.25-2.25H14.5v9h-3.757c-.71 0-1.4.201-1.992.572l.004-7.322zm-1.504 7.324l.004-5.073-.002-2.253A2.25 2.25 0 005.003 2.5H1.5v9h3.757a3.75 3.75 0 011.994.574z"></path></svg>
<% if (plugins.traffic.error) { %>
<%= plugins.traffic.error.message %>
<% } else { %>
<%= plugins.traffic.views.count %> view<%= s(plugins.traffic.views.count) %> in last two weeks
<% } %>
</div>
<% } %>
</section>
<section>
<div class="field calendar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 <%= computed.calendar.length*15 %> 11" width="<%= computed.calendar.length*15 %>" height="16">
<g>
<% for (const [x, {color}] of Object.entries(computed.calendar)) { %>
<rect class="day" x="<%= x*15 %>" y="0" width="11" height="11" fill="<%= color %>" rx="2" ry="2" />
<% } %>
</g>
</svg>
</div>
<% if (plugins.lines) { %>
<div class="field <%= plugins.lines.error ? 'error' : '' %>">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h10.5a.25.25 0 00.25-.25V4.664a.25.25 0 00-.073-.177l-2.914-2.914a.25.25 0 00-.177-.073H2.75zM1 1.75C1 .784 1.784 0 2.75 0h7.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0113.25 16H2.75A1.75 1.75 0 011 14.25V1.75zm7 1.5a.75.75 0 01.75.75v1.5h1.5a.75.75 0 010 1.5h-1.5v1.5a.75.75 0 01-1.5 0V7h-1.5a.75.75 0 010-1.5h1.5V4A.75.75 0 018 3.25zm-3 8a.75.75 0 01.75-.75h4.5a.75.75 0 010 1.5h-4.5a.75.75 0 01-.75-.75z"></path></svg>
<% if (plugins.lines.error) { %>
<%= plugins.lines.error.message %>
<% } else { %>
<%= plugins.lines.added %> added, <%= plugins.lines.deleted %> removed
<% } %>
</div>
<% } %>
</section>
</div>
</section>
<% } %>
<% if (plugins.followup) { %>
<div class="row">
<section class="column">
<h3>Issues</h3>
<% if (plugins.followup.error) { %>
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.followup.error.message %>
</div>
</section>
<% } else { %>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<mask id="issues-bar">
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
</mask>
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= plugins.followup.issues.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= (plugins.followup.issues.closed/plugins.followup.issues.count)*220 || 0 %>" height="8" fill="#d73a49"/>
<rect mask="url(#issues-bar)" x="<%= (plugins.followup.issues.closed/plugins.followup.issues.count)*220 || 0 %>" y="0" width="<%= (1-plugins.followup.issues.closed/plugins.followup.issues.count)*220 || 0 %>" height="8" fill="#28a745"/>
</svg>
<div class="field horizontal fill-width">
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d73a49" fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 0110.65-5.003.75.75 0 00.959-1.153 8 8 0 102.592 8.33.75.75 0 10-1.444-.407A6.5 6.5 0 011.5 8zM8 12a1 1 0 100-2 1 1 0 000 2zm0-8a.75.75 0 01.75.75v3.5a.75.75 0 11-1.5 0v-3.5A.75.75 0 018 4zm4.78 4.28l3-3a.75.75 0 00-1.06-1.06l-2.47 2.47-.97-.97a.749.749 0 10-1.06 1.06l1.5 1.5a.75.75 0 001.06 0z"></path></svg>
<span class="no-wrap"><%= plugins.followup.issues.closed %> Closed</span>
</div>
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#28a745" fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
<span class="no-wrap"><%= plugins.followup.issues.open %> Open</span>
</div>
</div>
<% } %>
</section>
<section class="column">
<h3>Pull requests</h3>
<% if (plugins.followup.error) { %>
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.followup.error.message %>
</div>
</section>
<% } else { %>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<mask id="pr-bar">
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
</mask>
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= plugins.followup.pr.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= (plugins.followup.pr.merged/plugins.followup.pr.count)*220 || 0 %>" height="8" fill="#6f42c1"/>
<rect mask="url(#pr-bar)" x="<%= (plugins.followup.pr.merged/plugins.followup.pr.count)*220 || 0 %>" y="0" width="<%= (1-plugins.followup.pr.merged/plugins.followup.pr.count)*220 || 0 %>" height="8" fill="#28a745"/>
</svg>
<div class="field horizontal fill-width">
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#6f42c1" fill-rule="evenodd" d="M5 3.254V3.25v.005a.75.75 0 110-.005v.004zm.45 1.9a2.25 2.25 0 10-1.95.218v5.256a2.25 2.25 0 101.5 0V7.123A5.735 5.735 0 009.25 9h1.378a2.251 2.251 0 100-1.5H9.25a4.25 4.25 0 01-3.8-2.346zM12.75 9a.75.75 0 100-1.5.75.75 0 000 1.5zm-8.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>
<span class="no-wrap"><%= plugins.followup.pr.merged %> Merged</span>
</div>
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#28a745" fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"></path></svg>
<span class="no-wrap"><%= plugins.followup.pr.open %> Open</span>
</div>
</div>
<% } %>
</section>
</div>
<% } %>
<% if (plugins.projects) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.75 0A1.75 1.75 0 000 1.75v12.5C0 15.216.784 16 1.75 16h12.5A1.75 1.75 0 0016 14.25V1.75A1.75 1.75 0 0014.25 0H1.75zM1.5 1.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v12.5a.25.25 0 01-.25.25H1.75a.25.25 0 01-.25-.25V1.75zM11.75 3a.75.75 0 00-.75.75v7.5a.75.75 0 001.5 0v-7.5a.75.75 0 00-.75-.75zm-8.25.75a.75.75 0 011.5 0v5.5a.75.75 0 01-1.5 0v-5.5zM8 3a.75.75 0 00-.75.75v3.5a.75.75 0 001.5 0v-3.5A.75.75 0 008 3z"></path></svg>
Active projects
</h2>
<div class="row">
<% if (plugins.projects.error) { %>
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
<%= plugins.projects.error.message %>
</div>
</section>
<% } else { %>
<section>
<% for (const {name, updated, progress} of plugins.projects.list) { %>
<div class="row fill-width">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14H1.75A1.75 1.75 0 010 12.25v-8.5zm1.75-.25a.25.25 0 00-.25.25v8.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-8.5a.25.25 0 00-.25-.25H1.75zM3.5 6.25a.75.75 0 01.75-.75h7a.75.75 0 010 1.5h-7a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5h4a.75.75 0 000-1.5h-4z"></path></svg>
<%= name %>
</div>
</section>
</div>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"></path></svg>
Updated <%= updated %>
</div>
</section>
<% if (progress.enabled) { %>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 1.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v7.736a.75.75 0 101.5 0V1.75A1.75 1.75 0 0011.25 0h-8.5A1.75 1.75 0 001 1.75v11.5c0 .966.784 1.75 1.75 1.75h3.17a.75.75 0 000-1.5H2.75a.25.25 0 01-.25-.25V1.75zM4.75 4a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-4.5zM4 7.75A.75.75 0 014.75 7h2a.75.75 0 010 1.5h-2A.75.75 0 014 7.75zm11.774 3.537a.75.75 0 00-1.048-1.074L10.7 14.145 9.281 12.72a.75.75 0 00-1.062 1.058l1.943 1.95a.75.75 0 001.055.008l4.557-4.45z"></path></svg>
<%= [progress.done ? `${progress.done} done` : "", progress.doing ? `${progress.doing} doing` : "", progress.todo ? `${progress.todo} todo` : ""].filter(str => str).join(" · ") %>
</div>
</section>
<% } %>
</div>
<% if (progress.enabled) { %>
<div class="field center horizontal-wrap ">
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="460" height="8">
<mask id="project-bar">
<rect x="0" y="0" width="460" height="8" fill="white" rx="5"/>
</mask>
<rect mask="url(#project-bar)" x="0" y="0" width="<%= (progress.done/progress.total)*460 %>" height="8" fill="#28A745"/>
<rect mask="url(#project-bar)" x="<%= (progress.done/progress.total)*460 %>" y="0" width="<%= (progress.doing/progress.total)*460 %>" height="8" fill="#6F42C1"/>
<rect mask="url(#project-bar)" x="<%= ((progress.done+progress.doing)/progress.total)*460 %>" y="0" width="<%= (progress.todo/progress.total)*460 %>" height="8" fill="#d1d5da"/>
</svg>
</div>
<% } %>
<% } %>
</section>
<% } %>
</div>
</section>
<% } %>
<% } %>
<% if (base.metadata) { %>
<footer>
<span>Last updated <%= new Date().toGMTString() %> with lowlighter/metrics@<%= meta.version %></span>
</footer>
<% } %>
</div>
</foreignObject>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

+60
View File
@@ -0,0 +1,60 @@
//Imports
import common from "./../common.mjs"
/** Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) {
//Check arguments
const {repo} = q
if (!repo) {
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`}})
return await common(...arguments)
}
//Retrieving single repository
console.debug(`metrics/compute/${login}/${repo} > retrieving single repository ${repo}`)
const {user:{repository}} = await graphql(queries.repository({login, repo}))
data.user.repositories.nodes = [repository]
//Get commit activity
console.debug(`metrics/compute/${login}/${repo} > querying api for commits`)
const commits = []
for (let page = 0; page < 1; page++) {
console.debug(`metrics/compute/${login}/${repo} > loading page ${page}`)
const {data} = await rest.repos.listCommits({owner:login, repo, per_page:100, page})
if (!data.length) {
console.debug(`metrics/compute/${login}/${repo} > no more page to load`)
break
}
commits.push(...data)
}
console.debug(`metrics/compute/${login}/${repo} > ${commits.length} commits loaded`)
//Override creation date and disk usage
data.user.createdAt = repository.createdAt
data.user.repositories.totalDiskUsage = repository.diskUsage
//Override contributions calendar
const days = 14
//Compute relative date for each contribution
const now = new Date()
now.setHours(0, 0, 0, 0)
const contributions = commits.map(({commit}) => Math.abs(Math.ceil((now - new Date(commit.committer.date))/(24*60*60*1000)))).slice(0, days)
//Count contributions per relative day
const calendar = new Array(days).fill(0)
for (const day of contributions)
calendar[day]++
const max = Math.max(...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)"}}))
//Override plugins parameters
q["projects.limit"] = 0
//Common
await common(...arguments)
await Promise.all(pending)
//Reformat projects name
data.plugins.projects.list.map(project => project.name = project.name.replace(`(${login}/${repo})`, "").trim())
}
-103
View File
@@ -1,103 +0,0 @@
query Metrics {
user(login: $login) {
databaseId
name
login
createdAt
avatarUrl
websiteUrl
isHireable
twitterUsername
gists {
totalCount
}
repositories(last: $repositories, isFork: false, ownerAffiliations: OWNER) {
totalCount
totalDiskUsage
nodes {
name
watchers {
totalCount
}
stargazers {
totalCount
}
languages(first: 4) {
edges {
size
node {
color
name
}
}
}
issues_open: issues(states: OPEN) {
totalCount
}
issues_closed: issues(states: CLOSED) {
totalCount
}
pr_open: pullRequests(states: OPEN) {
totalCount
}
pr_merged: pullRequests(states: MERGED) {
totalCount
}
releases {
totalCount
}
forkCount
licenseInfo {
spdxId
}
}
}
packages {
totalCount
}
starredRepositories {
totalCount
}
watching {
totalCount
}
sponsorshipsAsSponsor {
totalCount
}
sponsorshipsAsMaintainer {
totalCount
}
contributionsCollection {
totalRepositoriesWithContributedCommits
totalCommitContributions
restrictedContributionsCount
totalIssueContributions
totalPullRequestContributions
totalPullRequestReviewContributions
}
calendar:contributionsCollection(from: $calendar.from, to: $calendar.to) {
contributionCalendar {
weeks {
contributionDays {
color
}
}
}
}
repositoriesContributedTo {
totalCount
}
followers {
totalCount
}
following {
totalCount
}
issueComments {
totalCount
}
organizations {
totalCount
}
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
import common from "./../common.mjs"
/** Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
export default async function ({login, q}, {conf, data, rest, graphql, plugins, queries}, {s, pending, imports}) {
//Common
await common(...arguments)
//Disable optimization to keep white-spaces
+30 -18
View File
@@ -15,6 +15,7 @@
const __src = path.join(__dirname, "src")
const __plugins = path.join(__src, "plugins")
const __templates = path.join(__src, "templates")
const __queries = path.join(__src, "queries")
process.on("unhandledRejection", error => { throw error })
colors.enable()
@@ -70,7 +71,7 @@
return [`with:`, ...Object.entries({
token:"${{ secrets.METRICS_TOKEN }}",
dryrun:true,
repositories:1,
repositories:0,
template:"${{ matrix.template }}",
base:"",
plugins_errors_fatal:true,
@@ -109,26 +110,37 @@
console.log(`Generated action`.grey)
//Perform assets includes
const assets = {}
const templates = (await fs.promises.readdir(__templates)).filter(name => !/.*[.]mjs$/.test(name)).sort()
for (const name of templates) {
const files = [
`${__templates}/${name}/query.graphql`,
`${__templates}/${name}/image.svg`,
`${__templates}/${name}/style.css`,
`${__templates}/${name}/fonts.css`,
]
const [query, image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
assets[name] = {query, image, style, fonts}
console.log(`Prepared template ${name}`.grey)
{
const assets = {}
const templates = (await fs.promises.readdir(__templates)).filter(name => !/.*[.]mjs$/.test(name)).sort()
for (const name of templates) {
const files = [
`${__templates}/${name}/image.svg`,
`${__templates}/${name}/style.css`,
`${__templates}/${name}/fonts.css`,
].map(file => fs.existsSync(path.resolve(file)) ? file : file.replace(`${__templates}/${name}/`, `${__templates}/classic/`))
const [image, style, fonts] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
assets[name] = {image, style, fonts}
console.log(`Prepared template ${name}`.grey)
}
code = code.replace(/<#assets>/g, Buffer.from(JSON.stringify(assets)).toString("base64"))
console.log(`Included ${templates.length} templates to generated action`.grey)
}
//Perform queries includes
{
const assets = {}
const queries = (await fs.promises.readdir(__queries)).sort()
code = code.replace(/<#queries>/g, Buffer.from(JSON.stringify(assets)).toString("base64"))
console.log(`Included ${queries.length} queries to generated action`.grey)
}
code = code.replace(/<#assets>/g, Buffer.from(JSON.stringify(assets)).toString("base64"))
console.log(`Included ${templates.length} templates to generated action`.grey)
//Perform version include
const version = JSON.parse(await fs.promises.readFile(path.join(__dirname, "package.json"))).version
code = code.replace(/<#version>/g, version)
console.log(`Included version number (${version}) to generated action`.grey)
{
const version = JSON.parse(await fs.promises.readFile(path.join(__dirname, "package.json"))).version
code = code.replace(/<#version>/g, version)
console.log(`Included version number (${version}) to generated action`.grey)
}
//Minify
code = minify(code).code
+8 -44
View File
@@ -59,14 +59,8 @@ jobs:
<%- testcase({
plugin_pagespeed: true,
plugin_pagespeed_token: "${{ secrets.PAGESPEED_TOKEN }}",
}) %>
- name: ${{ matrix.template }} > Plugin > PageSpeed (detailed)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_pagespeed: true,
plugin_pagespeed_detailed: true,
plugin_pagespeed_token: "${{ secrets.PAGESPEED_TOKEN }}",
plugin_pagespeed_screenshot: true,
}) %>
- name: ${{ matrix.template }} > Plugin > Music (playlist - apple)
@@ -99,12 +93,6 @@ jobs:
}) %>
- name: ${{ matrix.template }} > Plugin > Isocalendar
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_isocalendar: true,
}) %>
- name: ${{ matrix.template }} > Plugin > Isocalendar (full year)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_isocalendar: true,
@@ -116,14 +104,6 @@ jobs:
<%- testcase({
plugin_habits: true,
plugin_habits_from: 5,
}) %>
- name: ${{ matrix.template }} > Plugin > Habits (charts)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_habits: true,
plugin_habits_from: 5,
plugin_habits_facts: false,
plugin_habits_charts: true,
}) %>
@@ -139,15 +119,10 @@ jobs:
plugin_followup: true,
}) %>
- name: ${{ matrix.template }} > Plugin > Lines
- name: ${{ matrix.template }} > Plugin > Lines and Traffic
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_lines: true,
}) %>
- name: ${{ matrix.template }} > Plugin > Traffic
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_traffic: true,
}) %>
@@ -157,38 +132,27 @@ jobs:
plugin_gists: true,
}) %>
- name: ${{ matrix.template }} > Plugin > Topics (stars)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_topics: true,
plugin_topics_sort: "stars",
}) %>
- name: ${{ matrix.template }} > Plugin > Topics (activity)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_topics: true,
plugin_topics_sort: "activity",
}) %>
- name: ${{ matrix.template }} > Plugin > Topics (starred)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_topics: true,
plugin_topics_sort: "starred",
plugin_topics_mode: "starred",
plugin_topics_sort: "random",
}) %>
- name: ${{ matrix.template }} > Plugin > Topics (random)
- name: ${{ matrix.template }} > Plugin > Topics (mastered)
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_topics: true,
plugin_topics_sort: "random",
plugin_topics_mode: "mastered",
plugin_topics_sort: "stars",
}) %>
- name: ${{ matrix.template }} > Plugin > Projects
uses: lowlighter/metrics@<%- release %>
<%- testcase({
plugin_projects: true,
plugin_projects_repositories: "lowlighter/metrics/projects/1",
plugin_projects_limit: 2,
}) %>