Use a docker image to support puppeteer (#7)
This commit is contained in:
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
# Based on https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-in-docker
|
||||
|
||||
# Base image
|
||||
FROM node:15-slim
|
||||
|
||||
# Install latest chrome dev package and fonts to support major charsets
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y wget gnupg ca-certificates libgconf-2-4 \
|
||||
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Uncomment to skip the chromium download when installing puppeteer
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
|
||||
ENV PUPPETEER_BROWSER_PATH "google-chrome-stable"
|
||||
|
||||
# Copy action
|
||||
COPY action/dist/index.js /index.js
|
||||
RUN chmod +x /index.js
|
||||
|
||||
# Execute action
|
||||
ENTRYPOINT node /index.js
|
||||
11
action.yml
11
action.yml
@@ -6,8 +6,8 @@ branding:
|
||||
icon: user-check
|
||||
color: gray-dark
|
||||
runs:
|
||||
using: node12
|
||||
main: action/dist/index.js
|
||||
using: docker
|
||||
image: Dockerfile
|
||||
|
||||
# Inputs
|
||||
inputs:
|
||||
@@ -125,7 +125,8 @@ inputs:
|
||||
default: no
|
||||
|
||||
# Music provider
|
||||
# This is required when you enable the music plugin
|
||||
# This is required in "recent" mode
|
||||
# This is optional in "playlist" mode, in this case it will be deduced from "plugin_music_playlist" url
|
||||
# Supported values are :
|
||||
# - "apple" for Apple Music
|
||||
# - "spotify" for Spotify
|
||||
@@ -147,11 +148,11 @@ inputs:
|
||||
# - "recent" : display recently played tracks
|
||||
plugin_music_mode:
|
||||
description: Use "recent" to display recently played music and "playlist" to display tracks randomly from a given playlist (*required if music plugin is enabled)
|
||||
default: ""
|
||||
default: "recent"
|
||||
|
||||
# Music playlist
|
||||
# Required when using "plugin_music_mode" as "playlist"
|
||||
# The embed playlist url (the one used for music player iframe)
|
||||
# Will default mode to "playlist" when set
|
||||
plugin_music_playlist:
|
||||
description: Embed playlist url
|
||||
default: ""
|
||||
|
||||
94473
action/dist/index.js
vendored
94473
action/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -35,7 +35,11 @@
|
||||
"base.repositories":"Repositories metrics",
|
||||
"base.metadata":"Metadata",
|
||||
},
|
||||
options:{},
|
||||
options:{
|
||||
"habits.from":100,
|
||||
"music.playlist":"",
|
||||
"music.mode":"playlist",
|
||||
},
|
||||
},
|
||||
templates:{
|
||||
list:templates,
|
||||
@@ -66,6 +70,10 @@
|
||||
.flatMap(([key, value]) => key === "base" ? Object.entries(value).map(([key, value]) => [`base.${key}`, value]) : [[key, value]])
|
||||
.filter(([key, value]) => /^base[.]\w+$/.test(key) ? !value : value)
|
||||
.map(([key, value]) => `${key}=${+value}`)
|
||||
//Plugins options
|
||||
const options = Object.entries(this.plugins.options)
|
||||
.filter(([key, value]) => `${value}`.length)
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||
//Template
|
||||
const template = (this.templates.selected !== templates[0]) ? [`template=${this.templates.selected}`] : []
|
||||
//Generated url
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<section class="steps">
|
||||
<div class="step">
|
||||
<h2>1. Enter your GitHub username</h2>
|
||||
<input type="text" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
|
||||
<input type="text" name="user" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
|
||||
</div>
|
||||
<div class="step">
|
||||
<h2>2. Select a template</h2>
|
||||
@@ -48,18 +48,20 @@
|
||||
{{ plugins.descriptions[plugin] || plugin }}
|
||||
</label>
|
||||
</div>
|
||||
<template v-if="(plugins.enabled.habits)||(plugins.enabled.music)">
|
||||
<h3>2.3 Configure additional plugins</h3>
|
||||
<div class="options">
|
||||
<label v-if="(plugins.enabled.music)&&(plugins.options['music.mode'] === 'playlist')">
|
||||
Playlist embed link
|
||||
<input type="text" v-model="plugins.options['music.playlist']" placeholder="https://embed.music.apple.com/en/playlist/">
|
||||
</label>
|
||||
<label v-if="plugins.enabled.habits">
|
||||
Number of events for habits
|
||||
<input type="number" v-model="plugins.options['habits.from']" min="1" max="100">
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<div class="palette">
|
||||
Generated metrics use transparency and colors which can be read on both light and dark modes, so everyone can see your stats whatever their preferred color scheme !
|
||||
<div class="palettes">
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="light"> ☀️ Light mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="dark"> 🌙 Night mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<h2>3. Generate your metrics</h2>
|
||||
@@ -78,6 +80,17 @@
|
||||
<template v-if="user">
|
||||
<button @click="generate" :disabled="generated.pending">{{ generated.pending ? "Working on it :)" : "Generate your metrics !" }}</button>
|
||||
</template>
|
||||
<div class="palette">
|
||||
Generated metrics use transparency and colors which can be read on both light and dark modes, so everyone can see your stats whatever their preferred color scheme !
|
||||
<div class="palettes">
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="light"> ☀️ Light mode
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" v-model="palette" value="dark"> 🌙 Night mode
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<h2>4. Embed these metrics on your GitHub profile</h2>
|
||||
|
||||
@@ -56,9 +56,11 @@
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[name=user] {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
input[type=text], select, button {
|
||||
min-width: 50%;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
option {
|
||||
text-align: center;
|
||||
@@ -133,6 +135,10 @@
|
||||
.plugins label, .palettes label {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
/* Code snippets */
|
||||
.code {
|
||||
display: flex;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//Setup
|
||||
export default function ({login, imports, rest, computed, pending, q}, {enabled = false, from:_from = 100} = {}) {
|
||||
export default function ({login, imports, rest, computed, pending, q}, {enabled = false, from:defaults = 100} = {}) {
|
||||
//Check if plugin is enabled and requirements are met
|
||||
if (!enabled)
|
||||
return computed.plugins.habits = null
|
||||
@@ -8,9 +8,11 @@
|
||||
console.debug(`metrics/compute/${login}/plugins > habits`)
|
||||
|
||||
//Parameters override
|
||||
let {"habits.from":from = defaults.from ?? 100} = q
|
||||
//Events
|
||||
const from = Math.max(1, Math.min(100, "habits.from" in q ? Number(q["habits.from"])||0 : _from))
|
||||
console.debug(`metrics/compute/${login}/plugins > habits > events = ${from}`)
|
||||
from = Math.max(1, Math.min(100, Number(from)))
|
||||
//Debug
|
||||
console.debug(`metrics/compute/${login}/plugins > habits > ${JSON.stringify({from})}`)
|
||||
|
||||
//Plugin execution
|
||||
pending.push(new Promise(async solve => {
|
||||
@@ -27,7 +29,7 @@
|
||||
//Compute commit hours
|
||||
const hours = commits.map(({created_at}) => (new Date(created_at)).getHours())
|
||||
for (const hour of hours)
|
||||
habits.commits.hours[hour] = (habits.commits.hours[hour] || 0) + 1
|
||||
habits.commits.hours[hour] = (habits.commits.hours[hour] ?? 0) + 1
|
||||
//Compute hour with most commits
|
||||
habits.commits.hour = hours.length ? Object.entries(habits.commits.hours).sort(([an, a], [bn, b]) => b - a).map(([hour, occurence]) => hour)[0] : NaN
|
||||
}
|
||||
@@ -42,7 +44,7 @@
|
||||
edited
|
||||
.filter(({status}) => status === "fulfilled")
|
||||
.map(({value}) => value)
|
||||
.flatMap(files => files.flatMap(file => (file.patch||"").match(/(?<=^[+])((?:\t)|(?: )) /gm)||[]))
|
||||
.flatMap(files => files.flatMap(file => (file.patch ?? "").match(/(?<=^[+])((?:\t)|(?: )) /gm) ?? []))
|
||||
.forEach(indent => habits.indents[/^\t/.test(indent) ? "tabs" : "spaces"]++)
|
||||
//Compute indent style
|
||||
habits.indents.style = habits.indents.spaces > habits.indents.tabs ? "spaces" : habits.indents.tabs > habits.indents.spaces ? "tabs" : ""
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
//Supported providers
|
||||
const providers = {
|
||||
apple:"Apple Music",
|
||||
spotify:"Spotify",
|
||||
apple:{
|
||||
name:"Apple Music",
|
||||
embed:/^https:..embed.music.apple.com.\w+.playlist/,
|
||||
},
|
||||
spotify:{
|
||||
name:"Spotify",
|
||||
embed:/^https:..open.spotify.com.embed.playlist/,
|
||||
},
|
||||
}
|
||||
|
||||
//Supported modes
|
||||
@@ -18,32 +24,39 @@
|
||||
if (!q.music)
|
||||
return computed.plugins.music = null
|
||||
console.debug(`metrics/compute/${login}/plugins > music`)
|
||||
const raw = {
|
||||
get provider() { return providers[provider]?.name ?? "" },
|
||||
get mode() { return modes[mode] ?? "Unconfigured music plugin"},
|
||||
}
|
||||
|
||||
//Parameters override and checks
|
||||
let {"music.provider":provider = null, "music.mode":mode = null, "music.playlist":playlist = null, "music.limit":limit = 4} = q
|
||||
//Auto-guess parameters
|
||||
if ((playlist)&&(mode === null))
|
||||
mode = "playlist"
|
||||
if ((playlist)&&(provider === null))
|
||||
for (const [name, {embed}] of Object.entries(providers))
|
||||
if (embed.test(playlist))
|
||||
provider = name
|
||||
if (!mode)
|
||||
mode = "recent"
|
||||
//Provider
|
||||
const provider = q["music.provider"]||""
|
||||
if (!(provider in providers))
|
||||
return computed.plugins.music = {error:provider ? `Unsupported provider "${provider}"` : `Missing provider`, mode:"Unconfigured music plugin"}
|
||||
console.debug(`metrics/compute/${login}/plugins > music > provider "${provider}"`)
|
||||
return computed.plugins.music = {...raw, error:provider ? `Unsupported provider "${provider}"` : `Missing provider`}
|
||||
//Mode
|
||||
const mode = q["music.mode"]||""
|
||||
if (!(mode in modes))
|
||||
return computed.plugins.music = {error:mode ? `Unsupported mode "${mode}"` : `Missing mode`, provider:providers[provider], mode:"Unconfigured music plugin"}
|
||||
console.debug(`metrics/compute/${login}/plugins > music > mode "${mode}"`)
|
||||
return computed.plugins.music = {...raw, error:`Unsupported mode "${mode}"`}
|
||||
//Playlist mode
|
||||
const playlist = q["music.playlist"]||""
|
||||
if (mode === "playlist") {
|
||||
if (!playlist)
|
||||
return computed.plugins.music = {error:`Missing playlist url`, provider:providers[provider], mode:modes[mode]}
|
||||
if ((provider === "spotify")&&(!/^https:..open.spotify.com.embed.playlist/.test(playlist)))
|
||||
return computed.plugins.music = {error:`Unsupported playlist url format`, provider:providers[provider], mode:modes[mode]}
|
||||
if ((provider === "apple")&&(!/^https:..embed.music.apple.com.\w+.playlist/.test(playlist)))
|
||||
return computed.plugins.music = {error:`Unsupported playlist url format`, provider:providers[provider], mode:modes[mode]}
|
||||
console.debug(`metrics/compute/${login}/plugins > music > playlist = ${playlist}`)
|
||||
return computed.plugins.music = {...raw, error:`Missing playlist url`}
|
||||
if (!providers[provider].embed.test(playlist))
|
||||
return computed.plugins.music = {...raw, error:`Unsupported playlist url format`}
|
||||
}
|
||||
//Limit
|
||||
const limit = Math.max(1, Math.min(100, "music.limit" in q ? Number(q["music.limit"])||0 : 4))
|
||||
console.debug(`metrics/compute/${login}/plugins > music > limit = ${limit}`)
|
||||
limit = Math.max(1, Math.min(100, Number(limit)))
|
||||
//Debug
|
||||
console.debug(`metrics/compute/${login}/plugins > habits > ${JSON.stringify({provider, mode, playlist, limit})}`)
|
||||
|
||||
//Plugin execution
|
||||
pending.push(new Promise(async solve => {
|
||||
@@ -57,7 +70,8 @@
|
||||
case "playlist":{
|
||||
//Start puppeteer and navigate to playlist
|
||||
console.debug(`metrics/compute/${login}/plugins > music > starting browser`)
|
||||
const browser = await imports.puppeteer.launch()
|
||||
const browser = await imports.puppeteer.launch({headless:true, executablePath:process.env.PUPPETEER_BROWSER_PATH, args:["--no-sandbox", "--disable-extensions", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]})
|
||||
console.debug(`metrics/compute/${login}/plugins > music > loaded ${await browser.version()}`)
|
||||
const page = await browser.newPage()
|
||||
console.debug(`metrics/compute/${login}/plugins > music > loading page`)
|
||||
await page.goto(playlist)
|
||||
@@ -174,7 +188,7 @@
|
||||
}
|
||||
//Save results
|
||||
console.debug(`metrics/compute/${login}/plugins > music > success`)
|
||||
computed.plugins.music = {provider:providers[provider], mode:modes[mode], tracks}
|
||||
computed.plugins.music = {...raw, tracks}
|
||||
solve()
|
||||
return
|
||||
}
|
||||
@@ -184,12 +198,12 @@
|
||||
catch (error) {
|
||||
//Plugin error
|
||||
if (error.status) {
|
||||
computed.plugins.music = {provider:providers[provider], mode:modes[mode], error:error.status}
|
||||
computed.plugins.music = {...raw, error:error.status}
|
||||
console.debug(`metrics/compute/${login}/plugins > music > error > ${error.status}`)
|
||||
return solve()
|
||||
}
|
||||
//Generic error
|
||||
computed.plugins.music = {provider:providers[provider], mode:modes[mode], error:`An error occured`}
|
||||
computed.plugins.music = {...raw, error:`An error occured`}
|
||||
console.debug(`metrics/compute/${login}/plugins > music > error`)
|
||||
console.debug(error)
|
||||
solve()
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
+ (!!computed.plugins.habits)*68
|
||||
+ (!!computed.plugins.languages)*96
|
||||
+ (!!computed.plugins.music)*64 + (computed.plugins.music ? computed.plugins.music.tracks ? 14+Math.max(0, computed.plugins.music.tracks.length-1)*36 : 0 : 0)
|
||||
+ Math.max(0, (((!!base.metadata)+(!!base.header)+((!!base.activity)||(!!base.community))+(!!base.repositories)+((!!computed.plugins.habits))+(!!computed.plugins.pagespeed)+(!!computed.plugins.languages)+(!!computed.plugins.music))-1))*4
|
||||
%>">
|
||||
|
||||
<defs><style><%= fonts %></style></defs>
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
@@ -39,7 +39,12 @@
|
||||
{traffic:1},
|
||||
{selfskip:1},
|
||||
{pagespeed:1},
|
||||
{followup:1, languages:1, habits:1, "habits.events":1, lines:1, traffic:1, selfskip:1, pagespeed:1},
|
||||
{music:1},
|
||||
{music:1, "music.mode":"recent"},
|
||||
{music:1, "music.mode":"recent", "music.provider":"apple"},
|
||||
{music:1, "music.mode":"recent", "music.provider":"spotify"},
|
||||
{music:1, "music.mode":"playlist"},
|
||||
{followup:1, languages:1, habits:1, "habits.events":1, lines:1, traffic:1, selfskip:1, pagespeed:1, music:1},
|
||||
]) {
|
||||
await test.metrics({graphql, rest, q:{template, repositories:1, ...q}})
|
||||
}
|
||||
@@ -58,6 +63,7 @@
|
||||
selfskip:{enabled:true},
|
||||
languages:{enabled:true},
|
||||
followup:{enabled:true},
|
||||
musit:{enabled:true},
|
||||
}
|
||||
|
||||
//Compute render
|
||||
|
||||
Reference in New Issue
Block a user