lighthouse-badges 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore CHANGED
@@ -1 +1,2 @@
1
- coverage/
1
+ coverage/
2
+ dist/
package/.eslintrc.yml CHANGED
@@ -2,4 +2,5 @@ extends: airbnb-base
2
2
  env:
3
3
  jest: true
4
4
  rules:
5
- no-underscore-dangle: warn
5
+ no-underscore-dangle: warn
6
+ import/no-import-module-exports: warn
@@ -14,20 +14,15 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
16
  - name: Checkout
17
- uses: actions/checkout@v2
18
-
19
- - name: Set up Docker Context for Buildx
20
- id: buildx-context
21
- run: docker context create builders
17
+ uses: actions/checkout@v3
22
18
 
23
19
  - name: Set up QEMU
24
- uses: docker/setup-qemu-action@v1
20
+ uses: docker/setup-qemu-action@v2
25
21
 
26
22
  - name: Set up Docker Buildx
27
- uses: docker/setup-buildx-action@v1
23
+ uses: docker/setup-buildx-action@v2
28
24
  with:
29
25
  version: latest
30
- endpoint: builders
31
26
 
32
27
  - name: Versions
33
28
  run: |
@@ -39,16 +34,17 @@ jobs:
39
34
  run: |
40
35
  npm install
41
36
  npm run lint
37
+ npm run build
42
38
 
43
39
  - name: Login to DockerHub
44
40
  if: github.ref == 'refs/heads/master'
45
- uses: docker/login-action@v1
41
+ uses: docker/login-action@v2
46
42
  with:
47
43
  username: ${{ secrets.DOCKERHUB_USERNAME }}
48
44
  password: ${{ secrets.DOCKERHUB_TOKEN }}
49
45
 
50
46
  - name: Docker Build
51
- uses: docker/build-push-action@v2
47
+ uses: docker/build-push-action@v3
52
48
  with:
53
49
  context: .
54
50
  file: ./Dockerfile
@@ -77,7 +73,7 @@ jobs:
77
73
  npm config set //npm.pkg.github.com/:_authToken ${GITHUB_TOKEN}
78
74
  [[ "$(npm show ${package_name} version)" == "${current_version}" ]] && \
79
75
  echo "Current version ${current_version} already published" || \
80
- npm publish --ignore-scripts
76
+ npm run ci:publish
81
77
  env:
82
78
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83
79
 
@@ -91,7 +87,7 @@ jobs:
91
87
  npm config set //registry.npmjs.org/:_authToken ${NPM_AUTH_TOKEN}
92
88
  [[ "$(npm show ${package_name} version)" == "${current_version}" ]] && \
93
89
  echo "Current version ${current_version} already published" || \
94
- npm publish --ignore-scripts
90
+ npm run ci:publish
95
91
  env:
96
92
  NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
97
93
 
@@ -101,7 +97,7 @@ jobs:
101
97
 
102
98
  - name: Deploy to Dockerhub
103
99
  if: github.ref == 'refs/heads/master'
104
- uses: docker/build-push-action@v2
100
+ uses: docker/build-push-action@v3
105
101
  with:
106
102
  context: .
107
103
  file: ./Dockerfile
package/Dockerfile CHANGED
@@ -15,15 +15,14 @@ LABEL maintainer="hello@mazzotta.me" \
15
15
  org.label-schema.version=$VERSION \
16
16
  org.label-schema.schema-version="1.0"
17
17
 
18
- # Update apk repositories & install chromium
19
- RUN apk --update --no-cache add chromium
18
+ RUN apk --update --no-cache add chromium git
20
19
 
20
+ # Add lighthouse
21
21
  RUN mkdir -p /home/lighthouse
22
22
  WORKDIR /home/lighthouse
23
+ COPY dist/src /home/lighthouse/src
24
+ COPY dist/package.json /home/lighthouse/package.json
23
25
 
24
- # Add lighthouse
25
- COPY src /home/lighthouse/src
26
- COPY package.json /home/lighthouse/package.json
27
26
  RUN npm install . && npm link && rm -rf /root/.npm
28
27
 
29
28
  # Set Chromium bin path
package/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Emanuele Mazzotta
3
+ Copyright (c) 2023 Emanuele Mazzotta
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -54,7 +54,7 @@ Required arguments:
54
54
  average score(s) of all the urls supplied, combined
55
55
  ```
56
56
 
57
- Additionally you can pass parameters to the lighthouse process directly via environment variable:
57
+ Additionally, you can pass parameters to the lighthouse process directly via environment variable:
58
58
 
59
59
  ```bash
60
60
  # This will pass '--preset=desktop' to the lighthouse process
@@ -1,12 +1,14 @@
1
- import { parser } from '../src/argparser';
1
+ import parser from '../src/argparser';
2
2
 
3
3
  describe('test argparser', () => {
4
+ const baseUrl = 'https://emanuelemazzotta.com';
5
+
4
6
  it('should return expected default values', () => {
5
- const actualArgs = parser.parse_args(['--urls', 'https://emanuelemazzotta.com', 'https://emanuelemazzotta.com/cv']);
7
+ const actualArgs = parser.parse_args(['--urls', baseUrl, `${baseUrl}/cv`]);
6
8
  expect(actualArgs.single_badge).toBe(false);
7
9
  expect(actualArgs.badge_style).toBe('flat');
8
10
  expect(actualArgs.save_report).toBe(false);
9
- expect(actualArgs.urls).toStrictEqual(['https://emanuelemazzotta.com', 'https://emanuelemazzotta.com/cv']);
11
+ expect(actualArgs.urls).toStrictEqual([baseUrl, `${baseUrl}/cv`]);
10
12
  });
11
13
 
12
14
  it('should overwrite values', () => {
@@ -14,12 +16,12 @@ describe('test argparser', () => {
14
16
  '--single-badge',
15
17
  '--save-report',
16
18
  '--badge-style', 'flat-square',
17
- '--urls', 'https://emanuelemazzotta.com',
19
+ '--urls', baseUrl,
18
20
  ]);
19
21
 
20
22
  expect(actualArgs.single_badge).toBe(true);
21
23
  expect(actualArgs.badge_style).toBe('flat-square');
22
24
  expect(actualArgs.save_report).toBe(true);
23
- expect(actualArgs.urls).toStrictEqual(['https://emanuelemazzotta.com']);
25
+ expect(actualArgs.urls).toStrictEqual([baseUrl]);
24
26
  });
25
27
  });
@@ -1,5 +1,5 @@
1
- import { parser } from '../src/argparser';
2
- import { handleUserInput } from '../src/index';
1
+ import parser from '../src/argparser';
2
+ import handleUserInput from '../src/index';
3
3
  import * as lighthouseBadges from '../src/lighthouse-badges';
4
4
 
5
5
  jest.mock('../src/lighthouse-badges');
@@ -1,12 +1,12 @@
1
1
  import fs from 'fs';
2
- import ReportGenerator from 'lighthouse/report/generator/report-generator';
3
- import lighthouseBadges, {
2
+ import {
4
3
  htmlReportsToFile,
5
4
  metricsToSvg,
5
+ processParameters,
6
6
  processRawLighthouseResult,
7
7
  } from '../src/lighthouse-badges';
8
8
  import { zip } from '../src/util';
9
- import { parser } from '../src/argparser';
9
+ import parser from '../src/argparser';
10
10
  import reportFixture from '../assets/report/emanuelemazzotta.com.json';
11
11
 
12
12
  describe('test lighthouse badges', () => {
@@ -14,7 +14,7 @@ describe('test lighthouse badges', () => {
14
14
  it('should return correct metrics and no report', async () => {
15
15
  const url = 'https://emanuelemazzotta.com';
16
16
  const shouldSaveReport = false;
17
- const result = await processRawLighthouseResult(reportFixture, url, shouldSaveReport);
17
+ const result = await processRawLighthouseResult(reportFixture, '', url, shouldSaveReport);
18
18
  expect({
19
19
  metrics: {
20
20
  'lighthouse performance': 98,
@@ -30,10 +30,15 @@ describe('test lighthouse badges', () => {
30
30
  });
31
31
 
32
32
  it('should return correct metrics and a valid report', async () => {
33
- const expectedHtmlReport = ReportGenerator.generateReportHtml(reportFixture);
33
+ const expectedHtmlReport = '<html>Fake report</html>';
34
34
  const url = 'https://emanuelemazzotta.com';
35
35
  const shouldSaveReport = true;
36
- const result = await processRawLighthouseResult(reportFixture, url, shouldSaveReport);
36
+ const result = await processRawLighthouseResult(
37
+ reportFixture,
38
+ expectedHtmlReport,
39
+ url,
40
+ shouldSaveReport,
41
+ );
37
42
  expect({
38
43
  metrics: {
39
44
  'lighthouse performance': 98,
@@ -142,8 +147,8 @@ describe('test lighthouse badges', () => {
142
147
  ]);
143
148
 
144
149
  const calculateLighthouseMetrics = jest.fn();
145
- calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, 'https://example.org', args.save_report));
146
- await lighthouseBadges.processParameters(args, calculateLighthouseMetrics);
150
+ calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, '<html>Fake report</html>', 'https://example.org', args.save_report));
151
+ await processParameters(args, calculateLighthouseMetrics);
147
152
 
148
153
  expect(output.length).toBe(2);
149
154
  });
@@ -155,8 +160,8 @@ describe('test lighthouse badges', () => {
155
160
  ]);
156
161
 
157
162
  const calculateLighthouseMetrics = jest.fn();
158
- calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, 'https://example.org', args.save_report));
159
- await lighthouseBadges.processParameters(args, calculateLighthouseMetrics);
163
+ calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, '<html>Fake report</html>', 'https://example.org', args.save_report));
164
+ await processParameters(args, calculateLighthouseMetrics);
160
165
 
161
166
  expect(output.length).toBe(6);
162
167
  });
@@ -168,8 +173,8 @@ describe('test lighthouse badges', () => {
168
173
  ]);
169
174
 
170
175
  const calculateLighthouseMetrics = jest.fn();
171
- calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, 'https://example.org', args.save_report));
172
- await lighthouseBadges.processParameters(args, calculateLighthouseMetrics);
176
+ calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, null, 'https://example.org', args.save_report));
177
+ await processParameters(args, calculateLighthouseMetrics);
173
178
 
174
179
  expect(output.length).toBe(1);
175
180
  });
@@ -180,8 +185,8 @@ describe('test lighthouse badges', () => {
180
185
  ]);
181
186
 
182
187
  const calculateLighthouseMetrics = jest.fn();
183
- calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, 'https://example.org', args.save_report));
184
- await lighthouseBadges.processParameters(args, calculateLighthouseMetrics);
188
+ calculateLighthouseMetrics.mockReturnValue(await processRawLighthouseResult(reportFixture, null, 'https://example.org', args.save_report));
189
+ await processParameters(args, calculateLighthouseMetrics);
185
190
 
186
191
  expect(output.length).toBe(5);
187
192
  });
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Emanuele Mazzotta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/README.md ADDED
@@ -0,0 +1,103 @@
1
+ [![Build Status](https://github.com/emazzotta/lighthouse-badges/workflows/build/badge.svg)](https://github.com/emazzotta/lighthouse-badges/actions)
2
+ [![Code Coverage](https://codecov.io/gh/emazzotta/lighthouse-badges/branch/master/graph/badge.svg)](https://github.com/emazzotta/lighthouse-badges/actions)
3
+ [![NPM downloads](https://img.shields.io/npm/dt/lighthouse-badges?color=blue)](https://www.npmjs.org/package/lighthouse-badges)
4
+ [![NPM version](https://img.shields.io/npm/v/lighthouse-badges.svg)](https://www.npmjs.org/package/lighthouse-badges)
5
+ [![License](http://img.shields.io/:license-mit-blue.svg?style=flat)](https://emanuelemazzotta.com/mit-license)
6
+
7
+ # Lighthouse Badges
8
+
9
+ [![Lighthouse](./assets/img/lighthouse.svg)](https://github.com/GoogleChrome/lighthouse)
10
+
11
+ This package allows you to easily create Lighthouse badges for all Lighthouse categories.
12
+ Ever wanted to brag about your sites's awesome Lighthouse performance? Then this is the package for you!
13
+
14
+ ## Examples
15
+
16
+ ### All Badges
17
+
18
+ [![Lighthouse Accessibility Badge](./assets/img/scores/lighthouse_accessibility.svg)](https://github.com/emazzotta/lighthouse-badges)
19
+ [![Lighthouse Best Practices Badge](./assets/img/scores/lighthouse_best-practices.svg)](https://github.com/emazzotta/lighthouse-badges)
20
+ [![Lighthouse Performance Badge](./assets/img/scores/lighthouse_performance.svg)](https://github.com/emazzotta/lighthouse-badges)
21
+ [![Lighthouse PWA Badge](./assets/img/scores/lighthouse_pwa.svg)](https://github.com/emazzotta/lighthouse-badges)
22
+ [![Lighthouse SEO Badge](./assets/img/scores/lighthouse_seo.svg)](https://github.com/emazzotta/lighthouse-badges)
23
+
24
+ ### Single Badge
25
+
26
+ [![Lighthouse](./assets/img/scores/lighthouse.svg)](https://github.com/emazzotta/lighthouse-badges)
27
+
28
+ ## Usage
29
+
30
+ ### Help
31
+
32
+ ```txt
33
+ usage: lighthouse-badges [-h] [-v] [-s]
34
+ [-b {flat,flat-square,plastic,for-the-badge,popout,popout-square,social}]
35
+ [-o OUTPUT_PATH] [-r] -u URLS [URLS ...]
36
+
37
+
38
+ Generate gh-badges (shields.io) based on lighthouse performance.
39
+
40
+ Optional arguments:
41
+ -h, --help Show this help message and exit.
42
+ -v, --version Show program's version number and exit.
43
+ -s, --single-badge Output only one single badge averaging all lighthouse
44
+ categories' scores
45
+ -b {flat,flat-square,plastic,for-the-badge,popout,popout-square,social}, --badge-style {flat,flat-square,plastic,for-the-badge,popout,popout-square,social}
46
+ Define look and feel for the badge
47
+ -o OUTPUT_PATH, --output-path OUTPUT_PATH
48
+ Define output path for artifacts
49
+ -r, --save-report Save lighthouse report as html for every supplied url
50
+
51
+ Required arguments:
52
+ -u URLS [URLS ...], --urls URLS [URLS ...]
53
+ The lighthouse badge(s) will contain the respective
54
+ average score(s) of all the urls supplied, combined
55
+ ```
56
+
57
+ Additionally, you can pass parameters to the lighthouse process directly via environment variable:
58
+
59
+ ```bash
60
+ # This will pass '--preset=desktop' to the lighthouse process
61
+ export LIGHTHOUSE_BADGES_PARAMS="--preset=desktop"
62
+ lighthouse-badges --urls https://www.youtube.com/
63
+ ```
64
+
65
+ ### Run
66
+
67
+ Hint: Only node >= 12 is supported.
68
+
69
+ #### Option 1: npm
70
+ ```bash
71
+ npm i -g lighthouse-badges
72
+ lighthouse-badges --urls https://www.youtube.com/ https://www.youtube.com/feed/trending -o test_results
73
+ ```
74
+
75
+ #### Option 2: npx
76
+ ```bash
77
+ npx lighthouse-badges --urls https://www.youtube.com/ https://www.youtube.com/feed/trending -o test_results
78
+ ```
79
+
80
+ #### Option 3: Docker
81
+ ```bash
82
+ # Warning, the docker version may alter the lighthouse results
83
+ docker run --rm \
84
+ -v $PWD/test_results:/home/chrome/reports \
85
+ emazzotta/lighthouse-badges \
86
+ /bin/sh -c "lighthouse-badges --urls https://www.youtube.com/ https://www.youtube.com/feed/trending"
87
+ ```
88
+
89
+ ## Contributing
90
+
91
+ See [contribution guideline](./CONTRIBUTING.md)
92
+
93
+ ## Sponsors
94
+
95
+ Sponsored by [JetBrains](https://www.jetbrains.com/?from=Lighthouse-Badges)
96
+
97
+ <a href="https://www.jetbrains.com/?from=Lighthouse-Badges">
98
+ <img alt="Jetbrains Logo" src="./assets/img/jetbrains.svg" height="100">
99
+ </a>
100
+
101
+ ## Author
102
+
103
+ [Emanuele Mazzotta](mailto:hello@mazzotta.me)
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "lighthouse-badges",
3
+ "version": "1.2.0",
4
+ "description": "🚦Generate gh-badges (shields.io) based on Lighthouse performance.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "lighthouse-badges": "src/index.js"
8
+ },
9
+ "scripts": {
10
+ "app:install": "npm i -g .",
11
+ "app:reinstall": "npm-run-all app:uninstall app:install",
12
+ "app:uninstall": "npm uninstall -g @emazzotta/lighthouse-badges",
13
+ "build": "npm run clean && babel src --out-dir dist/src && sed 's#\"dist/src/index.js\"#\"src/index.js\"#' package.json > ./dist/package.json && cp README.md dist && cp LICENSE.md dist",
14
+ "ci:publish": "npm run build && npm publish --prefix dist",
15
+ "clean": "rm -rf *.svg *.html coverage dist results",
16
+ "docker:build": "npm run build && docker build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +\"%Y-%m-%dT%H:%M:%SZ\"` --build-arg VERSION=latest -t emazzotta/lighthouse-badges .",
17
+ "docker:system-test:run": "docker run emazzotta/lighthouse-badges /bin/sh -c 'lighthouse-badges -rsu https://google.com && EXEC_PATH=/home/chrome/reports npm run system-test:verify --prefix /home/lighthouse'",
18
+ "lint": "eslint .",
19
+ "lint:fix": "eslint --fix .",
20
+ "pre-push": "npm-run-all lint:fix test:all",
21
+ "run:global-installation": "[ -z ${PAGE} ] && PAGE=https://google.com ; lighthouse-badges -rsu ${PAGE} -o results",
22
+ "run:local-installation": "[ -z ${PAGE} ] && PAGE=https://google.com ; node dist/src/index.js -rsu ${PAGE} -o results",
23
+ "system-test:full-run": "npm-run-all clean build app:reinstall run:global-installation system-test:verify",
24
+ "system-test:light-run": "npm-run-all clean build run:local-installation system-test:verify",
25
+ "system-test:verify": "[ -z ${EXEC_PATH} ] && EXEC_PATH=$PWD/results ; grep -q '<svg xmlns' ${EXEC_PATH}/*.svg && grep -q '<title>Lighthouse Report</title>' ${EXEC_PATH}/*.html",
26
+ "test": "jest",
27
+ "test:all": "npm-run-all test system-test:full-run",
28
+ "test:related": "jest --findRelatedTests"
29
+ },
30
+ "husky": {
31
+ "hooks": {
32
+ "pre-commit": "lint-staged"
33
+ }
34
+ },
35
+ "lint-staged": {
36
+ "*.js": [
37
+ "npm run lint:fix",
38
+ "npm run test:related"
39
+ ]
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/emazzotta/lighthouse-badges.git"
44
+ },
45
+ "dependencies": {
46
+ "argparse": "2.0.1",
47
+ "badge-maker": "^3.3.1",
48
+ "chrome-launcher": "^0.15.1",
49
+ "clui": "^0.3.6",
50
+ "lighthouse": "^10.0.0",
51
+ "ramda": "^0.28.0"
52
+ },
53
+ "devDependencies": {
54
+ "@babel/cli": "^7.20.7",
55
+ "@babel/core": "^7.20.12",
56
+ "@babel/plugin-transform-runtime": "^7.19.6",
57
+ "@babel/preset-env": "^7.20.2",
58
+ "@types/jest": "^29.4.0",
59
+ "eslint": "^8.33.0",
60
+ "eslint-config-airbnb-base": "^15.0.0",
61
+ "eslint-plugin-import": "^2.27.5",
62
+ "husky": "^8.0.3",
63
+ "jest": "^29.4.2",
64
+ "lint-staged": "^13.1.1",
65
+ "npm-run-all": "^4.1.5"
66
+ },
67
+ "engines": {
68
+ "npm": ">=7.0.0",
69
+ "node": ">=16.0.0"
70
+ },
71
+ "packageManager": "yarn@1.21.1",
72
+ "private": false,
73
+ "author": "Emanuele Mazzotta",
74
+ "license": "MIT"
75
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports["default"] = void 0;
7
+ var _argparse = require("argparse");
8
+ var _package = require("../package.json");
9
+ var parser = new _argparse.ArgumentParser({
10
+ add_help: true,
11
+ description: 'Generate gh-badges (shields.io) based on lighthouse performance.'
12
+ });
13
+ var requiredArgs = parser.add_argument_group({
14
+ title: 'Required arguments'
15
+ });
16
+ parser.add_argument('-v', '--version', {
17
+ action: 'version',
18
+ version: _package.version
19
+ });
20
+ parser.add_argument('-s', '--single-badge', {
21
+ action: 'store_true',
22
+ required: false,
23
+ help: 'Output only one single badge averaging all lighthouse categories\' scores '
24
+ });
25
+ parser.add_argument('-b', '--badge-style', {
26
+ action: 'store',
27
+ required: false,
28
+ choices: ['flat', 'flat-square', 'plastic', 'for-the-badge', 'popout', 'popout-square', 'social'],
29
+ "default": 'flat',
30
+ help: 'Define look and feel for the badge'
31
+ });
32
+ parser.add_argument('-o', '--output-path', {
33
+ action: 'store',
34
+ required: false,
35
+ help: 'Define output path for artifacts'
36
+ });
37
+ parser.add_argument('-r', '--save-report', {
38
+ action: 'store_true',
39
+ required: false,
40
+ help: 'Save lighthouse report as html for every supplied url'
41
+ });
42
+ requiredArgs.add_argument('-u', '--urls', {
43
+ action: 'store',
44
+ required: true,
45
+ nargs: _argparse.ONE_OR_MORE,
46
+ help: 'The lighthouse badge(s) will contain the respective average score(s) of all the urls supplied, combined'
47
+ });
48
+ var _default = parser;
49
+ exports["default"] = _default;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.percentageToColor = exports.getSquashedScore = exports.getAverageScore = void 0;
9
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
12
+ var R = _interopRequireWildcard(require("ramda"));
13
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
14
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15
+ var percentageToColor = function percentageToColor(percentage) {
16
+ if (percentage >= 95) return 'brightgreen';
17
+ if (percentage >= 90) return 'green';
18
+ if (percentage >= 75) return 'yellowgreen';
19
+ if (percentage >= 60) return 'yellow';
20
+ if (percentage >= 40) return 'orange';
21
+ return 'red';
22
+ };
23
+ exports.percentageToColor = percentageToColor;
24
+ var getAverageScore = /*#__PURE__*/function () {
25
+ var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(metrics) {
26
+ return _regenerator["default"].wrap(function _callee$(_context) {
27
+ while (1) switch (_context.prev = _context.next) {
28
+ case 0:
29
+ return _context.abrupt("return", R.pipe(R.head, R.keys, R.map(function (category) {
30
+ return (0, _defineProperty2["default"])({}, category, Math.round(R.sum(R.pluck(category, metrics)) / R.length(metrics)));
31
+ }), R.mergeAll)(metrics));
32
+ case 1:
33
+ case "end":
34
+ return _context.stop();
35
+ }
36
+ }, _callee);
37
+ }));
38
+ return function getAverageScore(_x) {
39
+ return _ref.apply(this, arguments);
40
+ };
41
+ }();
42
+ exports.getAverageScore = getAverageScore;
43
+ var getSquashedScore = /*#__PURE__*/function () {
44
+ var _ref3 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(metrics) {
45
+ return _regenerator["default"].wrap(function _callee2$(_context2) {
46
+ while (1) switch (_context2.prev = _context2.next) {
47
+ case 0:
48
+ return _context2.abrupt("return", {
49
+ lighthouse: R.pipe(R.map(function (metric) {
50
+ return R.sum(R.values(metric));
51
+ }), R.sum, function (x) {
52
+ return x / (R.length(metrics) * R.length(R.keys(R.head(metrics))));
53
+ }, R.curry(Math.round))(metrics)
54
+ });
55
+ case 1:
56
+ case "end":
57
+ return _context2.stop();
58
+ }
59
+ }, _callee2);
60
+ }));
61
+ return function getSquashedScore(_x2) {
62
+ return _ref3.apply(this, arguments);
63
+ };
64
+ }();
65
+ exports.getSquashedScore = getSquashedScore;
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports["default"] = void 0;
9
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
11
+ var _clui = _interopRequireDefault(require("clui"));
12
+ var _lighthouseBadges = require("./lighthouse-badges");
13
+ var _argparser = _interopRequireDefault(require("./argparser"));
14
+ var handleUserInput = /*#__PURE__*/function () {
15
+ var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(spinner) {
16
+ return _regenerator["default"].wrap(function _callee$(_context) {
17
+ while (1) switch (_context.prev = _context.next) {
18
+ case 0:
19
+ _context.prev = 0;
20
+ if (process.env.LIGHTHOUSE_BADGES_PARAMS) {
21
+ process.stdout.write("LIGHTHOUSE_BADGES_PARAMS: ".concat(process.env.LIGHTHOUSE_BADGES_PARAMS, "\n"));
22
+ }
23
+ spinner.start();
24
+ _context.t0 = _lighthouseBadges.processParameters;
25
+ _context.next = 6;
26
+ return _argparser["default"].parse_args();
27
+ case 6:
28
+ _context.t1 = _context.sent;
29
+ _context.t2 = _lighthouseBadges.calculateLighthouseMetrics;
30
+ _context.next = 10;
31
+ return (0, _context.t0)(_context.t1, _context.t2);
32
+ case 10:
33
+ spinner.stop();
34
+ _context.next = 17;
35
+ break;
36
+ case 13:
37
+ _context.prev = 13;
38
+ _context.t3 = _context["catch"](0);
39
+ process.stderr.write("".concat(_context.t3, "\n"));
40
+ process.exit(1);
41
+ case 17:
42
+ case "end":
43
+ return _context.stop();
44
+ }
45
+ }, _callee, null, [[0, 13]]);
46
+ }));
47
+ return function handleUserInput(_x) {
48
+ return _ref.apply(this, arguments);
49
+ };
50
+ }();
51
+
52
+ // Only self-invoke if not imported but called directly as executable
53
+ (function () {
54
+ return !module.parent && handleUserInput(new _clui["default"].Spinner('Running Lighthouse, please wait...', ['◜', '◠', '◝', '◞', '◡', '◟']));
55
+ })();
56
+ var _default = handleUserInput;
57
+ exports["default"] = _default;
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _typeof = require("@babel/runtime/helpers/typeof");
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.processRawLighthouseResult = exports.processParameters = exports.metricsToSvg = exports.htmlReportsToFile = exports.calculateLighthouseMetrics = void 0;
9
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
11
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
12
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
13
+ var _badgeMaker = require("badge-maker");
14
+ var _path = _interopRequireDefault(require("path"));
15
+ var _fs = _interopRequireDefault(require("fs"));
16
+ var R = _interopRequireWildcard(require("ramda"));
17
+ var chromeLauncher = _interopRequireWildcard(require("chrome-launcher"));
18
+ var _index = _interopRequireDefault(require("lighthouse/core/index.cjs"));
19
+ var _util = require("./util");
20
+ var _calculations = require("./calculations");
21
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
22
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
23
+ var metricsToSvg = /*#__PURE__*/function () {
24
+ var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(lighthouseMetrics, badgeStyle, outputPath) {
25
+ return _regenerator["default"].wrap(function _callee$(_context) {
26
+ while (1) switch (_context.prev = _context.next) {
27
+ case 0:
28
+ R.keys(lighthouseMetrics).map(function (lighthouseMetricKey) {
29
+ var filepath = _path["default"].join(outputPath, "".concat(lighthouseMetricKey.replace(/ /g, '_'), ".svg"));
30
+ var badgeColor = (0, _calculations.percentageToColor)(lighthouseMetrics[lighthouseMetricKey]);
31
+ var svg = (0, _badgeMaker.makeBadge)({
32
+ label: lighthouseMetricKey,
33
+ message: "".concat(lighthouseMetrics[lighthouseMetricKey], "%"),
34
+ color: badgeColor,
35
+ style: badgeStyle
36
+ });
37
+ _fs["default"].writeFile(filepath, svg, function (error) {
38
+ return (0, _util.statusMessage)("Saved svg to ".concat(filepath, "\n"), "Failed to save svg to ".concat(outputPath), error);
39
+ });
40
+ return true;
41
+ });
42
+ case 1:
43
+ case "end":
44
+ return _context.stop();
45
+ }
46
+ }, _callee);
47
+ }));
48
+ return function metricsToSvg(_x, _x2, _x3) {
49
+ return _ref.apply(this, arguments);
50
+ };
51
+ }();
52
+ exports.metricsToSvg = metricsToSvg;
53
+ var htmlReportsToFile = /*#__PURE__*/function () {
54
+ var _ref2 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(htmlReports, outputPath) {
55
+ return _regenerator["default"].wrap(function _callee2$(_context2) {
56
+ while (1) switch (_context2.prev = _context2.next) {
57
+ case 0:
58
+ return _context2.abrupt("return", htmlReports.map(function (report) {
59
+ var url = R.head(R.keys(report));
60
+ if (report[url]) {
61
+ var filepath = _path["default"].join(outputPath, "".concat((0, _util.urlEscaper)(url), ".html"));
62
+ _fs["default"].writeFile(filepath, report[url], function (error) {
63
+ return (0, _util.statusMessage)("Saved report to ".concat(filepath, "\n"), "Failed to save report to ".concat(outputPath), error);
64
+ });
65
+ }
66
+ return false;
67
+ }));
68
+ case 1:
69
+ case "end":
70
+ return _context2.stop();
71
+ }
72
+ }, _callee2);
73
+ }));
74
+ return function htmlReportsToFile(_x4, _x5) {
75
+ return _ref2.apply(this, arguments);
76
+ };
77
+ }();
78
+ exports.htmlReportsToFile = htmlReportsToFile;
79
+ var generateArtifacts = /*#__PURE__*/function () {
80
+ var _ref4 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(_ref3) {
81
+ var reports, svg, outputPath;
82
+ return _regenerator["default"].wrap(function _callee3$(_context3) {
83
+ while (1) switch (_context3.prev = _context3.next) {
84
+ case 0:
85
+ reports = _ref3.reports, svg = _ref3.svg, outputPath = _ref3.outputPath;
86
+ _context3.next = 3;
87
+ return Promise.all([htmlReportsToFile(reports, outputPath), metricsToSvg(svg.results, svg.style, outputPath)]);
88
+ case 3:
89
+ case "end":
90
+ return _context3.stop();
91
+ }
92
+ }, _callee3);
93
+ }));
94
+ return function generateArtifacts(_x6) {
95
+ return _ref4.apply(this, arguments);
96
+ };
97
+ }();
98
+ var processRawLighthouseResult = /*#__PURE__*/function () {
99
+ var _ref5 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(data, html, url, shouldSaveReport) {
100
+ var htmlReport, categories, scores, lighthouseMetrics;
101
+ return _regenerator["default"].wrap(function _callee4$(_context4) {
102
+ while (1) switch (_context4.prev = _context4.next) {
103
+ case 0:
104
+ htmlReport = shouldSaveReport ? html : false;
105
+ categories = data.categories;
106
+ scores = R.keys(categories).map(function (category) {
107
+ return (0, _defineProperty2["default"])({}, "lighthouse ".concat(category.toLowerCase()), categories[category].score * 100);
108
+ });
109
+ lighthouseMetrics = Object.assign.apply(Object, [{}].concat((0, _toConsumableArray2["default"])(scores)));
110
+ return _context4.abrupt("return", {
111
+ metrics: lighthouseMetrics,
112
+ report: (0, _defineProperty2["default"])({}, url, htmlReport)
113
+ });
114
+ case 5:
115
+ case "end":
116
+ return _context4.stop();
117
+ }
118
+ }, _callee4);
119
+ }));
120
+ return function processRawLighthouseResult(_x7, _x8, _x9, _x10) {
121
+ return _ref5.apply(this, arguments);
122
+ };
123
+ }();
124
+ exports.processRawLighthouseResult = processRawLighthouseResult;
125
+ var calculateLighthouseMetrics = /*#__PURE__*/function () {
126
+ var _ref7 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5(url, shouldSaveReport) {
127
+ var additionalParams,
128
+ chromeParameters,
129
+ chrome,
130
+ options,
131
+ runnerResult,
132
+ reportHtml,
133
+ reportJson,
134
+ _args5 = arguments;
135
+ return _regenerator["default"].wrap(function _callee5$(_context5) {
136
+ while (1) switch (_context5.prev = _context5.next) {
137
+ case 0:
138
+ additionalParams = _args5.length > 2 && _args5[2] !== undefined ? _args5[2] : '';
139
+ chromeParameters = ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--no-default-browser-check', '--no-first-run', '--disable-default-apps', '--output=json', '--output-path=stdout', '--quiet', additionalParams];
140
+ _context5.next = 4;
141
+ return chromeLauncher.launch({
142
+ chromeFlags: chromeParameters
143
+ });
144
+ case 4:
145
+ chrome = _context5.sent;
146
+ options = {
147
+ logLevel: 'silent',
148
+ output: 'html',
149
+ port: chrome.port
150
+ };
151
+ _context5.next = 8;
152
+ return (0, _index["default"])(url, options);
153
+ case 8:
154
+ runnerResult = _context5.sent;
155
+ reportHtml = runnerResult.report;
156
+ reportJson = runnerResult.lhr;
157
+ _context5.next = 13;
158
+ return chrome.kill();
159
+ case 13:
160
+ return _context5.abrupt("return", processRawLighthouseResult(reportJson, reportHtml, url, shouldSaveReport));
161
+ case 14:
162
+ case "end":
163
+ return _context5.stop();
164
+ }
165
+ }, _callee5);
166
+ }));
167
+ return function calculateLighthouseMetrics(_x11, _x12) {
168
+ return _ref7.apply(this, arguments);
169
+ };
170
+ }();
171
+ exports.calculateLighthouseMetrics = calculateLighthouseMetrics;
172
+ var processParameters = /*#__PURE__*/function () {
173
+ var _ref8 = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6(args, func) {
174
+ var outputPath, additionalParams, results, metrics, reports, metricsResults;
175
+ return _regenerator["default"].wrap(function _callee6$(_context6) {
176
+ while (1) switch (_context6.prev = _context6.next) {
177
+ case 0:
178
+ outputPath = args.output_path || process.cwd();
179
+ _fs["default"].mkdir(outputPath, {
180
+ recursive: true
181
+ }, function (err) {
182
+ if (err) throw err;
183
+ });
184
+ additionalParams = process.env.LIGHTHOUSE_BADGES_PARAMS || '';
185
+ _context6.next = 5;
186
+ return Promise.all(args.urls.map(function (url) {
187
+ return func(url, args.save_report, additionalParams);
188
+ }));
189
+ case 5:
190
+ results = _context6.sent;
191
+ metrics = R.pluck('metrics', results);
192
+ reports = R.pluck('report', results);
193
+ if (!args.single_badge) {
194
+ _context6.next = 14;
195
+ break;
196
+ }
197
+ _context6.next = 11;
198
+ return (0, _calculations.getSquashedScore)(metrics);
199
+ case 11:
200
+ _context6.t0 = _context6.sent;
201
+ _context6.next = 17;
202
+ break;
203
+ case 14:
204
+ _context6.next = 16;
205
+ return (0, _calculations.getAverageScore)(metrics);
206
+ case 16:
207
+ _context6.t0 = _context6.sent;
208
+ case 17:
209
+ metricsResults = _context6.t0;
210
+ _context6.next = 20;
211
+ return generateArtifacts({
212
+ reports: reports,
213
+ svg: {
214
+ results: metricsResults,
215
+ style: args.badge_style
216
+ },
217
+ outputPath: outputPath
218
+ });
219
+ case 20:
220
+ case "end":
221
+ return _context6.stop();
222
+ }
223
+ }, _callee6);
224
+ }));
225
+ return function processParameters(_x13, _x14) {
226
+ return _ref8.apply(this, arguments);
227
+ };
228
+ }();
229
+ exports.processParameters = processParameters;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.zip = exports.urlEscaper = exports.statusMessage = void 0;
7
+ var urlEscaper = function urlEscaper(url) {
8
+ return url.toLowerCase().replace(/(^\w+:|^)\/\//, '').replace(/[^a-z0-9]/g, '_');
9
+ };
10
+ exports.urlEscaper = urlEscaper;
11
+ var zip = function zip(rows) {
12
+ return rows[0].map(function (_, c) {
13
+ return rows.map(function (row) {
14
+ return row[c];
15
+ });
16
+ });
17
+ };
18
+ exports.zip = zip;
19
+ var statusMessage = function statusMessage(successMessage, errorMessage, error) {
20
+ if (error) {
21
+ throw new Error(errorMessage);
22
+ }
23
+ process.stdout.write(successMessage);
24
+ };
25
+ exports.statusMessage = statusMessage;
package/jest.config.js ADDED
@@ -0,0 +1,16 @@
1
+ module.exports = async () => ({
2
+ collectCoverageFrom: [
3
+ 'src/**/*.{js,jsx,mjs}',
4
+ ],
5
+ collectCoverage: true,
6
+ coverageReporters: [
7
+ 'json',
8
+ 'html',
9
+ ],
10
+ transform: {
11
+ '^.+\\.(js|jsx)$': 'babel-jest',
12
+ },
13
+ transformIgnorePatterns: [
14
+ '/node_modules/(?!lighthouse)',
15
+ ],
16
+ });
package/package.json CHANGED
@@ -1,26 +1,30 @@
1
1
  {
2
2
  "name": "lighthouse-badges",
3
- "version": "1.1.26",
3
+ "version": "1.2.0",
4
4
  "description": "🚦Generate gh-badges (shields.io) based on Lighthouse performance.",
5
- "main": "src/index.js",
5
+ "main": "dist/src/index.js",
6
6
  "bin": {
7
- "lighthouse-badges": "src/index.js"
7
+ "lighthouse-badges": "dist/src/index.js"
8
8
  },
9
9
  "scripts": {
10
- "clean": "rm -f *.svg *.html",
11
- "docker:build": "docker build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +\"%Y-%m-%dT%H:%M:%SZ\"` --build-arg VERSION=latest -t emazzotta/lighthouse-badges .",
10
+ "app:install": "npm i -g .",
11
+ "app:reinstall": "npm-run-all app:uninstall app:install",
12
+ "app:uninstall": "npm uninstall -g @emazzotta/lighthouse-badges",
13
+ "build": "npm run clean && babel src --out-dir dist/src && sed 's#\"dist/src/index.js\"#\"src/index.js\"#' package.json > ./dist/package.json && cp README.md dist && cp LICENSE.md dist",
14
+ "ci:publish": "npm run build && npm publish --prefix dist",
15
+ "clean": "rm -rf *.svg *.html coverage dist results",
16
+ "docker:build": "npm run build && docker build --build-arg VCS_REF=`git rev-parse --short HEAD` --build-arg BUILD_DATE=`date -u +\"%Y-%m-%dT%H:%M:%SZ\"` --build-arg VERSION=latest -t emazzotta/lighthouse-badges .",
12
17
  "docker:system-test:run": "docker run emazzotta/lighthouse-badges /bin/sh -c 'lighthouse-badges -rsu https://google.com && EXEC_PATH=/home/chrome/reports npm run system-test:verify --prefix /home/lighthouse'",
13
18
  "lint": "eslint .",
14
19
  "lint:fix": "eslint --fix .",
15
- "pre-push": "npm-run-all lint:fix test system-test:full-run",
16
- "reinstall": "npm uninstall -g . && npm i -g .",
17
- "run:global-installation": "[ -z ${PAGE} ] && PAGE=https://google.com ; lighthouse-badges -rsu ${PAGE}",
18
- "run:local-installation": "[ -z ${PAGE} ] && PAGE=https://google.com ; node ./src/index.js -rsu ${PAGE}",
19
- "system-test:full-run": "npm-run-all clean reinstall run:global-installation system-test:verify clean",
20
- "system-test:light-run": "npm-run-all clean run:local-installation system-test:verify clean",
21
- "system-test:start-server": "npx http-server -a 127.0.0.1 -p 8080 &",
22
- "system-test:verify": "[ -z ${EXEC_PATH} ] && EXEC_PATH=$PWD ; grep -q '<svg xmlns' ${EXEC_PATH}/*.svg && grep -q '<title>Lighthouse Report</title>' ${EXEC_PATH}/*.html",
20
+ "pre-push": "npm-run-all lint:fix test:all",
21
+ "run:global-installation": "[ -z ${PAGE} ] && PAGE=https://google.com ; lighthouse-badges -rsu ${PAGE} -o results",
22
+ "run:local-installation": "[ -z ${PAGE} ] && PAGE=https://google.com ; node dist/src/index.js -rsu ${PAGE} -o results",
23
+ "system-test:full-run": "npm-run-all clean build app:reinstall run:global-installation system-test:verify",
24
+ "system-test:light-run": "npm-run-all clean build run:local-installation system-test:verify",
25
+ "system-test:verify": "[ -z ${EXEC_PATH} ] && EXEC_PATH=$PWD/results ; grep -q '<svg xmlns' ${EXEC_PATH}/*.svg && grep -q '<title>Lighthouse Report</title>' ${EXEC_PATH}/*.html",
23
26
  "test": "jest",
27
+ "test:all": "npm-run-all test system-test:full-run",
24
28
  "test:related": "jest --findRelatedTests"
25
29
  },
26
30
  "husky": {
@@ -41,34 +45,31 @@
41
45
  "dependencies": {
42
46
  "argparse": "2.0.1",
43
47
  "badge-maker": "^3.3.1",
48
+ "chrome-launcher": "^0.15.1",
44
49
  "clui": "^0.3.6",
45
- "lighthouse": "^9.6.6",
50
+ "lighthouse": "^10.0.0",
46
51
  "ramda": "^0.28.0"
47
52
  },
48
53
  "devDependencies": {
49
- "@babel/core": "^7.18.10",
50
- "@babel/plugin-transform-runtime": "^7.18.10",
51
- "@babel/preset-env": "^7.18.10",
52
- "@babel/runtime": "^7.18.9",
53
- "babel-jest": "^28.1.3",
54
- "eslint": "^8.22.0",
54
+ "@babel/cli": "^7.20.7",
55
+ "@babel/core": "^7.20.12",
56
+ "@babel/plugin-transform-runtime": "^7.19.6",
57
+ "@babel/preset-env": "^7.20.2",
58
+ "@types/jest": "^29.4.0",
59
+ "eslint": "^8.33.0",
55
60
  "eslint-config-airbnb-base": "^15.0.0",
56
- "eslint-plugin-import": "^2.26.0",
57
- "husky": "^8.0.1",
58
- "jest": "^28.1.3",
59
- "lint-staged": "^13.0.3",
61
+ "eslint-plugin-import": "^2.27.5",
62
+ "husky": "^8.0.3",
63
+ "jest": "^29.4.2",
64
+ "lint-staged": "^13.1.1",
60
65
  "npm-run-all": "^4.1.5"
61
66
  },
62
- "jest": {
63
- "collectCoverageFrom": [
64
- "src/**/*.{js,jsx,mjs}"
65
- ],
66
- "collectCoverage": true,
67
- "coverageReporters": [
68
- "json",
69
- "html"
70
- ]
67
+ "engines": {
68
+ "npm": ">=7.0.0",
69
+ "node": ">=16.0.0"
71
70
  },
71
+ "packageManager": "yarn@1.21.1",
72
+ "private": false,
72
73
  "author": "Emanuele Mazzotta",
73
74
  "license": "MIT"
74
75
  }
package/src/argparser.js CHANGED
@@ -1,5 +1,5 @@
1
- const { ArgumentParser, ONE_OR_MORE } = require('argparse');
2
- const { version } = require('../package.json');
1
+ import { ArgumentParser, ONE_OR_MORE } from 'argparse';
2
+ import { version } from '../package.json';
3
3
 
4
4
  const parser = new ArgumentParser({
5
5
  add_help: true,
@@ -46,6 +46,4 @@ requiredArgs.add_argument('-u', '--urls', {
46
46
  help: 'The lighthouse badge(s) will contain the respective average score(s) of all the urls supplied, combined',
47
47
  });
48
48
 
49
- module.exports = {
50
- parser,
51
- };
49
+ export default parser;
@@ -1,6 +1,6 @@
1
- const R = require('ramda');
1
+ import * as R from 'ramda';
2
2
 
3
- const percentageToColor = (percentage) => {
3
+ export const percentageToColor = (percentage) => {
4
4
  if (percentage >= 95) return 'brightgreen';
5
5
  if (percentage >= 90) return 'green';
6
6
  if (percentage >= 75) return 'yellowgreen';
@@ -9,7 +9,7 @@ const percentageToColor = (percentage) => {
9
9
  return 'red';
10
10
  };
11
11
 
12
- const getAverageScore = async (metrics) => R.pipe(
12
+ export const getAverageScore = async (metrics) => R.pipe(
13
13
  R.head,
14
14
  R.keys,
15
15
  R.map((category) => (
@@ -17,7 +17,7 @@ const getAverageScore = async (metrics) => R.pipe(
17
17
  R.mergeAll,
18
18
  )(metrics);
19
19
 
20
- const getSquashedScore = async (metrics) => ({
20
+ export const getSquashedScore = async (metrics) => ({
21
21
  lighthouse: R.pipe(
22
22
  R.map((metric) => R.sum(R.values(metric))),
23
23
  R.sum,
@@ -25,9 +25,3 @@ const getSquashedScore = async (metrics) => ({
25
25
  R.curry(Math.round),
26
26
  )(metrics),
27
27
  });
28
-
29
- module.exports = {
30
- percentageToColor,
31
- getAverageScore,
32
- getSquashedScore,
33
- };
package/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const CLI = require('clui');
4
- const { processParameters, calculateLighthouseMetrics } = require('./lighthouse-badges');
5
- const { parser } = require('./argparser');
3
+ import CLI from 'clui';
4
+ import { calculateLighthouseMetrics, processParameters } from './lighthouse-badges';
5
+ import parser from './argparser';
6
6
 
7
7
  const handleUserInput = async (spinner) => {
8
8
  try {
@@ -21,6 +21,4 @@ const handleUserInput = async (spinner) => {
21
21
  // Only self-invoke if not imported but called directly as executable
22
22
  (() => !module.parent && handleUserInput(new CLI.Spinner('Running Lighthouse, please wait...', ['◜', '◠', '◝', '◞', '◡', '◟'])))();
23
23
 
24
- module.exports = {
25
- handleUserInput,
26
- };
24
+ export default handleUserInput;
@@ -1,19 +1,13 @@
1
- const { makeBadge } = require('badge-maker');
2
- const path = require('path');
3
- const fs = require('fs');
4
- const ReportGenerator = require('lighthouse/report/generator/report-generator');
5
- const { promisify } = require('util');
6
- const { exec } = require('child_process');
7
- const R = require('ramda');
8
- const { statusMessage } = require('./util');
9
- const { getAverageScore, getSquashedScore } = require('./calculations');
10
- const { urlEscaper } = require('./util');
11
- const { percentageToColor } = require('./calculations');
12
-
13
- // Buffer size for stdout, must be big enough to handle lighthouse CLI output
14
- const maxBuffer = 1024 * 50000;
15
-
16
- const metricsToSvg = async (lighthouseMetrics, badgeStyle, outputPath) => {
1
+ import { makeBadge } from 'badge-maker';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import * as R from 'ramda';
5
+ import * as chromeLauncher from 'chrome-launcher';
6
+ import lighthouse from 'lighthouse/core/index.cjs';
7
+ import { statusMessage, urlEscaper } from './util';
8
+ import { getAverageScore, getSquashedScore, percentageToColor } from './calculations';
9
+
10
+ export const metricsToSvg = async (lighthouseMetrics, badgeStyle, outputPath) => {
17
11
  R.keys(lighthouseMetrics).map((lighthouseMetricKey) => {
18
12
  const filepath = path.join(outputPath, `${lighthouseMetricKey.replace(/ /g, '_')}.svg`);
19
13
  const badgeColor = percentageToColor(lighthouseMetrics[lighthouseMetricKey]);
@@ -35,11 +29,11 @@ const metricsToSvg = async (lighthouseMetrics, badgeStyle, outputPath) => {
35
29
  });
36
30
  };
37
31
 
38
- const htmlReportsToFile = async (htmlReports, outputPath) => htmlReports.map((htmlReport) => {
39
- const url = R.head(R.keys(htmlReport));
40
- if (htmlReport[url]) {
32
+ export const htmlReportsToFile = async (htmlReports, outputPath) => htmlReports.map((report) => {
33
+ const url = R.head(R.keys(report));
34
+ if (report[url]) {
41
35
  const filepath = path.join(outputPath, `${urlEscaper(url)}.html`);
42
- fs.writeFile(filepath, htmlReport[url], (error) => statusMessage(
36
+ fs.writeFile(filepath, report[url], (error) => statusMessage(
43
37
  `Saved report to ${filepath}\n`,
44
38
  `Failed to save report to ${outputPath}`,
45
39
  error,
@@ -55,8 +49,8 @@ const generateArtifacts = async ({ reports, svg, outputPath }) => {
55
49
  ]);
56
50
  };
57
51
 
58
- const processRawLighthouseResult = async (data, url, shouldSaveReport) => {
59
- const htmlReport = shouldSaveReport ? ReportGenerator.generateReportHtml(data) : false;
52
+ export const processRawLighthouseResult = async (data, html, url, shouldSaveReport) => {
53
+ const htmlReport = shouldSaveReport ? html : false;
60
54
  const { categories } = data;
61
55
  const scores = R.keys(categories).map((category) => (
62
56
  { [`lighthouse ${category.toLowerCase()}`]: categories[category].score * 100 }
@@ -65,16 +59,31 @@ const processRawLighthouseResult = async (data, url, shouldSaveReport) => {
65
59
  return { metrics: lighthouseMetrics, report: { [url]: htmlReport } };
66
60
  };
67
61
 
68
- const calculateLighthouseMetrics = async (url, shouldSaveReport, additionalParams = '') => {
69
- const lighthouseBinary = path.join(__dirname, '..', 'node_modules', '.bin', 'lighthouse');
70
- const params = `--chrome-flags='--headless --no-sandbox --disable-gpu --disable-dev-shm-usage --no-default-browser-check --no-first-run --disable-default-apps' --output=json --output-path=stdout --quiet ${additionalParams}`;
71
- const lighthouseCommand = `${lighthouseBinary} ${params} ${url}`;
72
- const execPromise = promisify(exec);
73
- const { stdout } = await execPromise(`${lighthouseCommand}`, { maxBuffer });
74
- return processRawLighthouseResult(JSON.parse(stdout), url, shouldSaveReport);
62
+ export const calculateLighthouseMetrics = async (url, shouldSaveReport, additionalParams = '') => {
63
+ const chromeParameters = [
64
+ '--headless',
65
+ '--no-sandbox',
66
+ '--disable-gpu',
67
+ '--disable-dev-shm-usage',
68
+ '--no-default-browser-check',
69
+ '--no-first-run',
70
+ '--disable-default-apps',
71
+ '--output=json',
72
+ '--output-path=stdout',
73
+ '--quiet',
74
+ additionalParams,
75
+ ];
76
+ const chrome = await chromeLauncher.launch({ chromeFlags: chromeParameters });
77
+ const options = { logLevel: 'silent', output: 'html', port: chrome.port };
78
+ const runnerResult = await lighthouse(url, options);
79
+ const reportHtml = runnerResult.report;
80
+ const reportJson = runnerResult.lhr;
81
+ await chrome.kill();
82
+
83
+ return processRawLighthouseResult(reportJson, reportHtml, url, shouldSaveReport);
75
84
  };
76
85
 
77
- const processParameters = async (args, func) => {
86
+ export const processParameters = async (args, func) => {
78
87
  const outputPath = args.output_path || process.cwd();
79
88
 
80
89
  fs.mkdir(outputPath, { recursive: true }, (err) => {
@@ -99,11 +108,3 @@ const processParameters = async (args, func) => {
99
108
  outputPath,
100
109
  });
101
110
  };
102
-
103
- module.exports = {
104
- metricsToSvg,
105
- htmlReportsToFile,
106
- processRawLighthouseResult,
107
- calculateLighthouseMetrics,
108
- processParameters,
109
- };
package/src/util.js CHANGED
@@ -1,16 +1,10 @@
1
- const urlEscaper = (url) => url.toLowerCase().replace(/(^\w+:|^)\/\//, '').replace(/[^a-z0-9]/g, '_');
1
+ export const urlEscaper = (url) => url.toLowerCase().replace(/(^\w+:|^)\/\//, '').replace(/[^a-z0-9]/g, '_');
2
2
 
3
- const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c]));
3
+ export const zip = (rows) => rows[0].map((_, c) => rows.map((row) => row[c]));
4
4
 
5
- const statusMessage = (successMessage, errorMessage, error) => {
5
+ export const statusMessage = (successMessage, errorMessage, error) => {
6
6
  if (error) {
7
7
  throw new Error(errorMessage);
8
8
  }
9
9
  process.stdout.write(successMessage);
10
10
  };
11
-
12
- module.exports = {
13
- urlEscaper,
14
- zip,
15
- statusMessage,
16
- };