|
1
|
|
|
import * as core from '@actions/core'; |
|
2
|
|
|
import {Octokit} from '@octokit/rest'; |
|
3
|
|
|
import {config} from 'dotenv'; |
|
4
|
|
|
import {resolve} from 'path'; |
|
5
|
|
|
import * as chromeLauncher from 'chrome-launcher'; |
|
6
|
|
|
import * as lighthouse from 'lighthouse'; |
|
7
|
|
|
import {Flags} from 'lighthouse/types/externs'; |
|
8
|
|
|
|
|
9
|
|
|
// Load environment variables |
|
10
|
|
|
config({path: resolve(__dirname, '../.env')}); |
|
11
|
|
|
|
|
12
|
|
|
interface ActionInputs { |
|
13
|
|
|
GH_TOKEN: string; |
|
14
|
|
|
GIST_ID: string; |
|
15
|
|
|
TEST_URL: string; |
|
16
|
|
|
PRINT_SUMMARY: boolean; |
|
17
|
|
|
RESULT_BADGE: boolean; |
|
18
|
|
|
} |
|
19
|
|
|
|
|
20
|
|
|
const ACTION_URL = 'https://github.com/marketplace/actions/lighthouse-box'; |
|
21
|
|
|
const inputs: ActionInputs = { |
|
22
|
|
|
GH_TOKEN: core.getInput('GH_TOKEN', {required: true}), |
|
23
|
|
|
GIST_ID: core.getInput('GIST_ID', {required: true}), |
|
24
|
|
|
TEST_URL: core.getInput('TEST_URL', {required: true}), |
|
25
|
|
|
PRINT_SUMMARY: core.getBooleanInput('PRINT_SUMMARY', {required: true}), |
|
26
|
|
|
RESULT_BADGE: core.getBooleanInput('RESULT_BADGE', {required: true}), |
|
27
|
|
|
}; |
|
28
|
|
|
|
|
29
|
|
|
function getCurrentDate(): string { |
|
30
|
|
|
return new Date().toLocaleDateString('en-us', {day: 'numeric', year: 'numeric', month: 'short'}); |
|
31
|
|
|
} |
|
32
|
|
|
|
|
33
|
|
|
const UPDATE_DATE: string = getCurrentDate(); |
|
34
|
|
|
const summaryTable: any[] = []; |
|
35
|
|
|
const GIST_TITLE = `My website [update ${UPDATE_DATE}]`; |
|
36
|
|
|
|
|
37
|
|
|
async function fetchMetrics(testUrl: string): Promise<lighthouse.RunnerResult> { |
|
38
|
|
|
const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']}); |
|
39
|
|
|
const OPTIONS: Flags = {logLevel: 'info', output: 'json', port: chrome.port}; |
|
40
|
|
|
const runnerResult = await lighthouse(testUrl, OPTIONS); |
|
41
|
|
|
await chrome.kill(); |
|
42
|
|
|
return runnerResult; |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
function generateGistContent(runnerResult: lighthouse.RunnerResult): string { |
|
46
|
|
|
const {categories} = runnerResult.lhr; |
|
47
|
|
|
const metrics = { |
|
48
|
|
|
performance: categories.performance.score * 100, |
|
49
|
|
|
accessibility: categories.accessibility.score * 100, |
|
50
|
|
|
bestPractices: categories['best-practices'].score * 100, |
|
51
|
|
|
seo: categories.seo.score * 100, |
|
52
|
|
|
pwa: categories.pwa.score * 100, |
|
53
|
|
|
}; |
|
54
|
|
|
summaryTable.push([{data: 'Category', header: true}, {data: 'Result', header: true}]); |
|
55
|
|
|
return Object.entries(metrics).map(([category, score]) => { |
|
56
|
|
|
summaryTable.push([category, `${score}%`]); |
|
57
|
|
|
let badge = '🙉'; |
|
58
|
|
|
if (score > 80) badge = '🥈'; |
|
59
|
|
|
if (score > 90) badge = '🥇'; |
|
60
|
|
|
if (score === 100) badge = '🏆'; |
|
61
|
|
|
const title = `${category}:`.padEnd(inputs.RESULT_BADGE ? 37 : 49, '.'); |
|
62
|
|
|
const percent = `${score}%`.padStart(4, '.'); |
|
63
|
|
|
const result = inputs.RESULT_BADGE ? ` ${badge}`.padStart(11, '.') : ''; |
|
64
|
|
|
return `${title}${percent}${result}`; |
|
65
|
|
|
}).join('\n'); |
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
async function updateGistContent(gistId: string, content: string): Promise<void> { |
|
69
|
|
|
const octokit = new Octokit({auth: inputs.GH_TOKEN}); |
|
70
|
|
|
try { |
|
71
|
|
|
const gist = await octokit.gists.get({gist_id: gistId}); |
|
72
|
|
|
const filename = Object.keys(gist.data.files || {})[0]; |
|
73
|
|
|
if (!filename) { |
|
74
|
|
|
core.setFailed('Action failed: Gist filename not found'); |
|
75
|
|
|
return; |
|
76
|
|
|
} |
|
77
|
|
|
await octokit.gists.update({ |
|
78
|
|
|
gist_id: gistId, |
|
79
|
|
|
files: {[filename]: {filename: GIST_TITLE, content}}, |
|
80
|
|
|
}); |
|
81
|
|
|
} catch (error: any) { |
|
82
|
|
|
core.setFailed(`Action failed: Gist ${error.message}`); |
|
83
|
|
|
} |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
async function printLighthouseSummary(runnerResult: lighthouse.RunnerResult): Promise<void> { |
|
87
|
|
|
const summary = core.summary |
|
88
|
|
|
.addHeading('Results') |
|
89
|
|
|
.addTable(summaryTable) |
|
90
|
|
|
.addBreak() |
|
91
|
|
|
.addRaw('Lighthouse metrics for ') |
|
92
|
|
|
.addLink(runnerResult.lhr.mainDocumentUrl, runnerResult.lhr.mainDocumentUrl) |
|
93
|
|
|
.addRaw(' generated by ') |
|
94
|
|
|
.addLink('lighthouse-box/1.1', ACTION_URL); |
|
95
|
|
|
if (inputs.PRINT_SUMMARY) { |
|
96
|
|
|
await summary.write(); |
|
97
|
|
|
} else { |
|
98
|
|
|
console.log(summary.stringify()); |
|
99
|
|
|
} |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
(async () => { |
|
103
|
|
|
const runnerResult = await fetchMetrics(inputs.TEST_URL); |
|
104
|
|
|
const gistContent = generateGistContent(runnerResult); |
|
105
|
|
|
await updateGistContent(inputs.GIST_ID, gistContent); |
|
106
|
|
|
await printLighthouseSummary(runnerResult); |
|
107
|
|
|
})(); |
|
108
|
|
|
|