[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"projectName\": \"lighthouse-ci\",\n  \"projectOwner\": \"andreasonny83\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": true,\n  \"contributors\": [\n    {\n      \"login\": \"andreasonny83\",\n      \"name\": \"Andrea Sonny\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/8806300?v=4\",\n      \"profile\": \"https://about.me/andreasonny83\",\n      \"contributions\": [\n        \"question\",\n        \"code\",\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"celsosantarosa\",\n      \"name\": \"Celso Santa Rosa\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/1007970?v=4\",\n      \"profile\": \"https://snap-ci.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"BenAHammond\",\n      \"name\": \"Ben Hammond\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/3516389?v=4\",\n      \"profile\": \"https://github.com/BenAHammond\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"alexecus\",\n      \"name\": \"Alex Tenepere\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/12739106?v=4\",\n      \"profile\": \"https://github.com/alexecus\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ikigeg\",\n      \"name\": \"Michael Griffiths\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/8846301?v=4\",\n      \"profile\": \"https://ikigeg.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"cmarkwell\",\n      \"name\": \"Connor Markwell\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/23330646?v=4\",\n      \"profile\": \"https://github.com/cmarkwell\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Juuro\",\n      \"name\": \"Sebastian Engel\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/559017?v=4\",\n      \"profile\": \"https://github.com/Juuro\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"asmagin\",\n      \"name\": \"Alex Smagin\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/1803342?v=4\",\n      \"profile\": \"https://asmagin.com/\",\n      \"contributions\": [\n        \"code\",\n        \"ideas\"\n      ]\n    },\n    {\n      \"login\": \"marcschaller\",\n      \"name\": \"Marc Schaller\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/31402947?v=4\",\n      \"profile\": \"https://github.com/marcschaller\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Remi-p\",\n      \"name\": \"Rémi Perrot\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/6367611?v=4\",\n      \"profile\": \"https://github.com/Remi-p\",\n      \"contributions\": [\n        \"bug\",\n        \"code\"\n      ]\n    }\n  ],\n  \"commitConvention\": \"none\"\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[Makefile]\nindent_style = tab\nindent_size = 2\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Lighthouse\n/Lighthouse\n/demo/Lighthouse\nbudget.json\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# next.js build output\n.next\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless\n\n# VSCode\n.vscode/\n"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmjs.org/\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"trailingComma\": \"all\",\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"printWidth\": 120,\n  \"useTabs\": false,\n  \"tabWidth\": 2,\n  \"bracketSpacing\": true\n}\n"
  },
  {
    "path": ".snyk",
    "content": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.\nversion: v1.16.0\nignore: {}\n# patches apply the minimum changes required to fix a vulnerability\npatch:\n  SNYK-JS-LODASH-567746:\n    - lighthouse > inquirer > lodash:\n        patched: '2020-07-02T08:17:50.395Z'\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - '10.16'\nbefore_script:\n  - export DISPLAY=:99.0\n  - export CHROME_PATH=\"$(pwd)/chrome-linux/chrome\"\nservices:\n  - xvfb\naddons:\n  chrome: stable\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Lighthouse CI\n[![Build Status](https://travis-ci.com/andreasonny83/lighthouse-ci.svg?branch=main)](https://travis-ci.com/andreasonny83/lighthouse-ci)\n[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors)\n[![npm version](https://badge.fury.io/js/lighthouse-ci.svg)](https://badge.fury.io/js/lighthouse-ci)\n[![npm](https://img.shields.io/npm/dt/lighthouse-ci.svg)](https://www.npmjs.com/package/lighthouse-ci)\n[![Known Vulnerabilities](https://snyk.io/test/github/andreasonny83/lighthouse-ci/badge.svg?targetFile=package.json)](https://snyk.io/test/github/andreasonny83/lighthouse-ci?targetFile=package.json)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/xojs/xo)\n\n> A useful wrapper around Google Lighthouse CLI\n\n## NOTE\n\n#### Node v12 is now the minimum required version starting from Lighthouse CI v.1.13.0\n\n\n<img alt=\"Lighthouse CI logo\" src=\"https://raw.githubusercontent.com/andreasonny83/lighthouse-ci/main/logo.png\" width=\"800px\">\n\n<img src=\"https://raw.githubusercontent.com/andreasonny83/lighthouse-ci/main/lighthouse-cli.gif\" width=\"700\">\n\n## Install\n\n```\n$ npm install -g lighthouse-ci\n```\n\n## Table of Contents\n\n- [Lighthouse CI](#lighthouse-ci)\n  - [NOTE](#note)\n      - [Node v12 is now the minimum required version starting from Lighthouse CI v.1.13.0](#node-v12-is-now-the-minimum-required-version-starting-from-lighthouse-ci-v1130)\n  - [Install](#install)\n  - [Table of Contents](#table-of-contents)\n  - [Usage](#usage)\n  - [CLI](#cli)\n  - [Lighthouse flags](#lighthouse-flags)\n    - [Chrome flags](#chrome-flags)\n  - [Configuration](#configuration)\n  - [Budgets](#budgets)\n      - [Option 1.](#option-1)\n      - [Option 2.](#option-2)\n      - [Option 3.](#option-3)\n    - [Performance Budget](#performance-budget)\n    - [Timing Budget](#timing-budget)\n  - [Codechecks](#codechecks)\n  - [Demo App](#demo-app)\n  - [How to](#how-to)\n    - [Test a page that requires authentication](#test-a-page-that-requires-authentication)\n    - [Wait for post-load JavaScript to execute before ending a trace](#wait-for-post-load-javascript-to-execute-before-ending-a-trace)\n  - [Contributors](#contributors)\n  - [License](#license)\n\n## Usage\n\n```sh\nlighthouse-ci --help\n```\n\n## CLI\n\n```\n$ lighthouse-ci --help\n\n  Usage\n    $ lighthouse-ci <target-url>\n\n  Example\n    $ lighthouse-ci https://example.com\n    $ lighthouse-ci https://example.com -s\n    $ lighthouse-ci https://example.com --score=75\n    $ lighthouse-ci https://example.com --accessibility=90 --seo=80\n    $ lighthouse-ci https://example.com --accessibility=90 --seo=80 --report=folder\n    $ lighthouse-ci https://example.com --report=folder --config-path=configs.json\n\n  Options\n    -s, --silent                    Run Lighthouse without printing report log\n    --report=<path>                 Generate an HTML report inside a specified folder\n    --filename=<filename>           Specify the name of the generated HTML report file (requires --report)\n    -json, --jsonReport             Generate JSON report in addition to HTML (requires --report)\n    --config-path                   The path to the Lighthouse config JSON (read more here: https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md)\n    --budget-path                   The path to the Lighthouse budgets config JSON (read more here: https://developers.google.com/web/tools/lighthouse/audits/budgets)\n    --score=<threshold>             Specify a score threshold for the CI to pass\n    --performance=<threshold>       Specify a minimal performance score for the CI to pass\n    --pwa=<threshold>               Specify a minimal pwa score for the CI to pass\n    --accessibility=<threshold>     Specify a minimal accessibility score for the CI to pass\n    --best-practice=<threshold>     [DEPRECATED] Use best-practices instead\n    --best-practices=<threshold>    Specify a minimal best-practice score for the CI to pass\n    --seo=<threshold>               Specify a minimal seo score for the CI to pass\n    --fail-on-budgets               Specify CI should fail if budgets are exceeded\n    --budget.<counts|sizes>.<type>  Specify individual budget threshold (if --budget-path not set)\n\n  In addition to listed \"lighthouse-ci\" configuration flags, it is also possible to pass any native \"lighthouse\" flag\n  To see the full list of available flags, please refer to the official Google Lighthouse documentation at https://github.com/GoogleChrome/lighthouse#cli-options\n```\n\n## Lighthouse flags\n\nIn addition to listed `lighthouse-ci` configuration flags, it is also possible to pass any native `lighthouse` flags.\n\nTo see the full list of available flags, please refer to the official [Google Lighthouse documentation](https://github.com/GoogleChrome/lighthouse#cli-options).\n\neg.\n\n```sh\n# Launches browser, collects artifacts, saves them to disk (in `./test-report/`) and quits\n$ lighthouse-ci --gather-mode=test-report https://my.website.com\n# skips browser interaction, loads artifacts from disk (in `./test-report/`), runs audits on them, generates report\n$ lighthouse-ci --audit-mode=test-report https://my.website.com\n```\n\n### Chrome flags\n\nIn addition of the lighthouse flags, you can also specify extra chrome flags\ncomma separated.\n\neg.\n\n```sh\n$ lighthouse-ci --chrome-flags=--cellular-only,--force-ui-direction=rtl https://my.website.com\n```\n\neg.\n\n```sh\n$ lighthouse-ci --emulated-form-factor desktop --seo 92 https://my.website.com\n```\n\n## Configuration\n\nLighthouse CI allows you to pass a custom Lighthouse configuration file.\nRead [Lighthouse Configuration](https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md)\nto learn more about the configuration options available.\n\nJust generate your configuration file. For example this `config.json`\n\n```json\n{\n  \"extends\": \"lighthouse:default\",\n  \"audits\": [\n    \"user-timings\",\n    \"critical-request-chains\"\n  ],\n\n  \"categories\": {\n    \"performance\": {\n      \"name\": \"Performance Metrics\",\n      \"description\": \"Sample description\",\n      \"audits\": [\n        {\"id\": \"user-timings\", \"weight\": 1},\n        {\"id\": \"critical-request-chains\", \"weight\": 1}\n      ]\n    }\n  }\n}\n```\n\nThen run Lighthouse CI with the `--config-path` flag\n\n```sh\n$ lighthouse-ci https://example.com --report=reports --config-path=config.json\n```\n\nThe generated report inside `reports` folder will follow the custom configuration listed under the `config.json` file.\n\n## Budgets\n\nLighthouse CI allows you to pass a budget configuration file (see [Lighthouse Budgets](https://developers.google.com/web/tools/lighthouse/audits/budgets)).\nThere are several options to pass a budget config:\n\n#### Option 1.\nAdd configurations to your `config.json` file like and use instructions above.\n``` json\n{\n  \"extends\": \"lighthouse:default\",\n  \"settings\": {\n    \"budgets\": [\n      {\n        \"resourceCounts\": [\n          { \"resourceType\": \"total\", \"budget\": 10 },\n        ],\n        \"resourceSizes\": [\n          { \"resourceType\": \"total\", \"budget\": 100 },\n        ]\n      }\n    ]\n  }\n}\n```\n#### Option 2.\nGenerate `budget.json` with content like:\n``` json\n[\n  {\n    \"resourceCounts\": [\n      { \"resourceType\": \"total\", \"budget\": 10 },\n    ],\n    \"resourceSizes\": [\n      { \"resourceType\": \"total\", \"budget\": 100 },\n    ]\n  }\n]\n```\n\nThen run Lighthouse CI with the `--budget-path` flag\n\n```sh\n$ lighthouse-ci https://example.com --report=reports --budget-path=budget.json\n```\n\n#### Option 3.\nPass individual parameters via CLI\n\n```sh\n$ lighthouse-ci https://example.com --report=reports --budget.counts.total=20  --budget.sizes.fonts=100000\n```\n\n### Performance Budget\n\nPerformance budgets can be specified inside your [budget configuration file)(#budgets).\n\nYou can specify any available [performance budget](https://github.com/GoogleChrome/lighthouse/blob/master/docs/performance-budgets.md#budgetjson) like in the following example\n\n```json\n[\n  {\n    \"path\": \"/*\",\n    \"resourceSizes\": [\n      {\n        \"resourceType\": \"script\",\n        \"budget\": 400000\n      },\n      {\n        \"resourceType\": \"total\",\n        \"budget\": 5050\n      }\n    ],\n    \"resourceCounts\": [\n      {\n        \"resourceType\": \"total\",\n        \"budget\": 95\n      },\n      {\n        \"resourceType\": \"third-party\",\n        \"budget\": 55\n      }\n    ]\n  }\n]\n```\n\n### Timing Budget\n\nTiming budgets can be specified inside your [budget configuration file)(#budgets).\n\nYou can specify any available [timing budget](https://github.com/GoogleChrome/lighthouse/blob/master/docs/performance-budgets.md#timing-budgets) like in the following example\n\n```json\n[\n  {\n    \"path\": \"/*\",\n    \"timings\": [\n      {\n        \"metric\": \"interactive\",\n        \"budget\": 100\n      },\n      {\n        \"metric\": \"first-meaningful-paint\",\n        \"budget\": 100\n      }\n    ]\n  }\n]\n```\n\n## Codechecks\n\nYou can now easily integrate Lighthouse-CI as part of your automated CI with [codechecks.io](https://codechecks.io/).\n\n<img src=\"https://raw.githubusercontent.com/andreasonny83/lighthouse-ci/main/codechecks-01.png\" width=\"48%\"> <img src=\"https://raw.githubusercontent.com/andreasonny83/lighthouse-ci/main/codechecks-02.png\" width=\"48%\">\n\n**Running Lighthouse-CI with Codechecks**\n\n```sh\n$ npm install --save-dev @codechecks/client @codechecks/lighthouse-keeper\n```\n\nNow, create a `codechecks.yml` (json is supported as well) file required for codechecks to automatically run against your project.\n\n`codechecks.yml:`\n\n```yml\nchecks:\n  - name: lighthouse-keeper\n    options:\n      # just provide path to your build\n      buildPath: ./build\n      # or full url\n      # url: https://google.com\n  # ...\n```\n\nRead more from the official documentation from [https://github.com/codechecks/lighthouse-keeper](https://github.com/codechecks/lighthouse-keeper).\n\nRead more about Codechecks on the [official project website](https://codechecks.io/)\n\n## Demo App\n\nThis project contains a demo folder where a project as been created for demo purposes only.\nOnce inside the `demo` folder, if you have Docker installed on your machine, you can simply launch the demo app inside a Docker container with `make demo`.\n\nIf you just want to run the demo locally, make sure to install the node dependencies first with `npm install`,\nthen run the demo with:\n\n```\n$ npm start\n```\n\n## How to\n\n### Test a page that requires authentication\n\nBy default `lighthouse-cli` is just creating the report against a specific URL without letting the engineer to interact with the browser.\nSometimes, however, the page for which you want to generate the report, requires the user to be authenticated.\nDepending on the authentication mechanism, you can inject extra header information into the page.\n\n```sh\nlighthouse-ci https://example.com --extra-headers=./extra-headers.js\n```\n\nWhere `extra-headers.json` contains:\n\n```js\nmodule.exports = {\n  Authorization: 'Bearer MyAccessToken',\n  Cookie: \"user=MySecretCookie;\"\n};\n```\n\n### Wait for post-load JavaScript to execute before ending a trace\n\nYour website might require extra time to load and execute all the JavaScript logic.\nIt is possible to let LightHouse wait for a certain amount of time, before ending a trace,\nby providing a `pauseAfterLoadMs` value to a custom configuration file.\n\neg.\n\n```sh\nlighthouse-ci https://example.com --config-path ./config.json\n```\n\nWhere `config.json` contains:\n\n```json\n{\n  \"extends\": \"lighthouse:default\",\n  \"passes\": [{\n    \"recordTrace\": true,\n    \"pauseAfterLoadMs\": 5000,\n    \"networkQuietThresholdMs\": 5000\n  }]\n}\n```\n\n## Contributors\n\nThanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://about.me/andreasonny83\"><img src=\"https://avatars0.githubusercontent.com/u/8806300?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Andrea Sonny</b></sub></a><br /><a href=\"#question-andreasonny83\" title=\"Answering Questions\">💬</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=andreasonny83\" title=\"Code\">💻</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=andreasonny83\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://snap-ci.com\"><img src=\"https://avatars1.githubusercontent.com/u/1007970?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Celso Santa Rosa</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=celsosantarosa\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/BenAHammond\"><img src=\"https://avatars3.githubusercontent.com/u/3516389?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Ben Hammond</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/issues?q=author%3ABenAHammond\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=BenAHammond\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/alexecus\"><img src=\"https://avatars1.githubusercontent.com/u/12739106?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Alex Tenepere</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/issues?q=author%3Aalexecus\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=alexecus\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://ikigeg.com\"><img src=\"https://avatars0.githubusercontent.com/u/8846301?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Michael Griffiths</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=ikigeg\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/cmarkwell\"><img src=\"https://avatars0.githubusercontent.com/u/23330646?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Connor Markwell</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=cmarkwell\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/Juuro\"><img src=\"https://avatars2.githubusercontent.com/u/559017?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Sebastian Engel</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/issues?q=author%3AJuuro\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=Juuro\" title=\"Code\">💻</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://asmagin.com/\"><img src=\"https://avatars3.githubusercontent.com/u/1803342?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Alex Smagin</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=asmagin\" title=\"Code\">💻</a> <a href=\"#ideas-asmagin\" title=\"Ideas, Planning, & Feedback\">🤔</a></td>\n    <td align=\"center\"><a href=\"https://github.com/marcschaller\"><img src=\"https://avatars2.githubusercontent.com/u/31402947?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Marc Schaller</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/issues?q=author%3Amarcschaller\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=marcschaller\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://github.com/Remi-p\"><img src=\"https://avatars3.githubusercontent.com/u/6367611?v=4?s=100\" width=\"100px;\" alt=\"\"/><br /><sub><b>Rémi Perrot</b></sub></a><br /><a href=\"https://github.com/andreasonny83/lighthouse-ci/issues?q=author%3ARemi-p\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/andreasonny83/lighthouse-ci/commits?author=Remi-p\" title=\"Code\">💻</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!\n\n## License\n\nMIT\n\n---\n\nCreated with 🦄 by [andreasonny83](https://about.me/andreasonny83)\n"
  },
  {
    "path": "__tests__/budgets-analyzer.test.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst analyzeBudgets = require('../lib/budgets-analyzer');\n\ndescribe('budgets-analyzer', () => {\n  const processExit = process.exit;\n\n  beforeEach(() => {\n    process.exit = jest.fn();\n  });\n\n  afterEach(() => {\n    process.exit = processExit;\n  });\n\n  it('should return `false` if any over-budget reported and `--fail-on-budgets` set to `true`', () => {\n    const mockBudgetsReport = {\n      'total-count': '323 requests',\n      'total-size': '12677kb',\n    };\n\n    const result = analyzeBudgets(mockBudgetsReport, true);\n\n    expect(result).toEqual(false);\n  });\n\n  it('should return `true` if no over-budget reported and `--fail-on-budgets` set to `true`', () => {\n    const mockBudgetsReport = {};\n\n    const result = analyzeBudgets(mockBudgetsReport, true);\n\n    expect(result).toEqual(true);\n  });\n\n  it('should return `true` if any over-budget reported and `--fail-on-budgets` set to `false`', () => {\n    const mockBudgetsReport = {\n      'total-count': '323 requests',\n      'total-size': '12677kb',\n    };\n\n    const result = analyzeBudgets(mockBudgetsReport, true);\n\n    expect(result).toEqual(false);\n  });\n});\n"
  },
  {
    "path": "__tests__/helpers.test.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst mkdirp = require('mkdirp');\nconst rimraf = require('rimraf');\nconst {\n  clean,\n  createDir,\n  scoreReducer,\n  createDefaultConfig,\n  getOwnProps,\n  convertToBudgetList,\n  convertToResourceKey,\n} = require('../lib/helpers');\n\njest.mock('rimraf');\njest.mock('mkdirp');\n\ndescribe('helpers', () => {\n  beforeEach(() => {\n    jest.resetAllMocks();\n  });\n\n  describe('clean', () => {\n    it('should return a promise', () => {\n      // Arrange\n      const response = clean();\n\n      // Assert\n      expect(response).toBeInstanceOf(Promise);\n    });\n\n    it('should reject the promise if an error if present', () => {\n      // Arrange\n      const expectedError = 'err';\n      rimraf.mockImplementation((target, callback) => callback(expectedError));\n\n      // Act\n      const response = clean();\n\n      // Assert\n      expect(response).rejects.toEqual(expectedError);\n    });\n\n    it('should try to remove a \"lighthouse\" folder', () => {\n      // Arrange\n      const expectedFolderName = './lighthouse/';\n      rimraf.mockImplementation((target, callback) => {\n        expect(target).toEqual(expectedFolderName);\n        callback();\n      });\n\n      // Act\n      const response = clean();\n\n      // Assert\n      expect(response).resolves.toEqual();\n      expect(rimraf).toHaveBeenCalledWith(expectedFolderName, expect.any(Function));\n    });\n  });\n\n  describe('createDir', () => {\n    it('should return a promise', () => {\n      // Arrange\n      const response = createDir();\n\n      // Assert\n      expect(response).toBeInstanceOf(Promise);\n    });\n\n    it('should reject the promise if an error if present', () => {\n      // Arrange\n      const expectedError = 'err';\n      mkdirp.mockImplementation((target, callback) => callback(expectedError));\n\n      // Act\n      const response = createDir();\n\n      // Assert\n      expect(response).rejects.toEqual(expectedError);\n    });\n\n    it('should try to create a \"lighthouse\" folder', () => {\n      // Arrange\n      const expectedFolderName = './lighthouse';\n      mkdirp.mockImplementation((target, callback) => {\n        expect(target).toEqual(expectedFolderName);\n        callback();\n      });\n\n      // Act\n      const response = createDir();\n\n      // Assert\n      expect(response).resolves.toEqual();\n      expect(mkdirp).toHaveBeenCalledWith(expectedFolderName, expect.any(Function));\n    });\n  });\n\n  describe('scoreReducer', () => {\n    it('should return a score if present in the flags', () => {\n      // Arrange\n      const expectedScore = 'testScore';\n      const mockFlags = { score: expectedScore };\n\n      // Act\n      const response = scoreReducer(mockFlags);\n\n      // Assert\n      expect(response).toEqual(expectedScore);\n    });\n\n    it('should return an object of only known flags', () => {\n      // Arrange\n      const mockFlags = {\n        performance: '10',\n        accessibility: '90',\n        test: '10',\n      };\n      const mockScores = ['performance', 'pwa', 'accessibility'];\n      const expectedResponse = { accessibility: '90', performance: '10' };\n\n      // Act\n      const response = scoreReducer(mockFlags, mockScores);\n\n      // Assert\n      expect(response).toEqual(expectedResponse);\n    });\n  });\n\n  describe('getOwnProps', () => {\n    it('should return all own props', () => {\n      // Arrange\n      const expectedProp = 'foo';\n      const mockObject = { foo: 0 };\n\n      // Act\n      const response = getOwnProps(mockObject);\n\n      // Assert\n      expect(response).not.toBeNull();\n      expect(response).toContain(expectedProp);\n      expect(response).toHaveLength(1);\n    });\n\n    it('should not return inherited props', () => {\n      // Arrange\n      const expectedProp = 'foo';\n\n      const Func = function () {\n        this.foo = 1;\n      };\n\n      const input = new Func();\n\n      Func.prototype.bar = 2;\n\n      // Act\n      const response = getOwnProps(input);\n\n      // Assert\n      expect(response).not.toBeNull();\n      expect(response).toContain(expectedProp);\n      expect(response).toHaveLength(1);\n    });\n  });\n\n  describe('createDefaultConfig', () => {\n    it('should not override existing config', () => {\n      // Arrange\n      const expectedConfig = { foo: 1, bar: 2 };\n\n      // Act\n      const response = createDefaultConfig(expectedConfig);\n\n      // Assert\n      expect(response).toEqual(expectedConfig);\n    });\n\n    it('should set `extends` and `settings`', () => {\n      // Arrange\n      const expectedExtends = 'lighthouse:default';\n\n      // Act\n      const response = createDefaultConfig();\n\n      // Assert\n      expect(response.extends).toEqual(expectedExtends);\n      expect(response.settings).not.toBeNull();\n    });\n  });\n\n  describe('convertToBudgetList', () => {\n    it('Should return list of objects in specified format', () => {\n      // Arrange\n      const expected = [\n        {\n          resourceType: 'script',\n          budget: 1,\n        },\n        {\n          resourceType: 'total',\n          budget: 2,\n        },\n      ];\n\n      const input = { script: 1, total: 2 };\n\n      // Act\n      const response = convertToBudgetList(input);\n\n      // Assert\n      expect(response).toHaveLength(2);\n      expect(response).toEqual(expected);\n    });\n  });\n\n  describe('convertToResourceKey', () => {\n    it('Should convert `sizes` to `resourceSizes`', () => {\n      // Arrange\n      const expected = 'resourceSizes';\n\n      const input = 'sizes';\n\n      // Act\n      const response = convertToResourceKey(input);\n\n      // Assert\n      expect(response).toEqual(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "__tests__/reporter.test.js",
    "content": "const writeReport = require('../lib/lighthouse-reporter');\n\ndescribe('Reporter', () => {\n  jest.setTimeout(20000); // Allows more time to run all tests\n\n  it('should launch Chrome and generate a report', async () => {\n    const result = await writeReport('http://example.com/');\n    expect(result).toEqual(\n      expect.objectContaining({\n        categoryReport: {\n          performance: expect.any(Number),\n          accessibility: expect.any(Number),\n          'best-practices': expect.any(Number),\n          seo: expect.any(Number),\n          pwa: expect.any(Number),\n        },\n        budgetsReport: expect.any(Object),\n        htmlReport: expect.any(Object),\n        jsonReport: expect.any(Object),\n      }),\n    );\n  });\n});\n"
  },
  {
    "path": "__tests__/score-analyzer.test.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst analyzeScore = require('../lib/score-analyzer');\n\ndescribe('score-analyzer', () => {\n  const processExit = process.exit;\n\n  beforeEach(() => {\n    process.exit = jest.fn();\n  });\n\n  afterEach(() => {\n    process.exit = processExit;\n  });\n\n  it('a threshold must be specified', () => {\n    const mockCategoryReport = {\n      performance: 0.07,\n      pwa: 0.36,\n      accessibility: 0.57,\n      'best-practices': 0.69,\n      seo: 0.73,\n    };\n\n    expect(() => analyzeScore(mockCategoryReport)).toThrowError('Invalid threshold score.');\n  });\n\n  it('should return `false` if one or more category index is below the threshold', () => {\n    const threshold = 75;\n    const mockCategoryReport = {\n      performance: 7,\n      pwa: 36,\n      accessibility: 57,\n      'best-practices': 69,\n      seo: 73,\n    };\n\n    const result = analyzeScore(mockCategoryReport, threshold);\n\n    expect(result).toEqual(false);\n  });\n\n  it('should return `true` if all the category indexes are above the threshold', () => {\n    const threshold = 70;\n    const mockCategoryReport = {\n      performance: 70,\n      pwa: 90,\n      accessibility: 87,\n      'best-practices': 79,\n      seo: 73,\n    };\n\n    const result = analyzeScore(mockCategoryReport, threshold);\n\n    expect(result).toEqual(true);\n  });\n\n  describe('category thresholds', () => {\n    const mockCategoryReport = {\n      performance: 70,\n      pwa: 90,\n      accessibility: 10,\n      'best-practices': 10,\n      seo: 10,\n    };\n\n    it('should return `true` if the target categories indexes are above the thresholds', () => {\n      const threshold = {\n        performance: 70,\n        pwa: 80,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(true);\n    });\n\n    it('should pass if the target categories indexes are above the \"best-practices\" thresholds', () => {\n      const threshold = {\n        'best-practices': 10,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(true);\n    });\n\n    it('should pass if the target categories indexes are above the \"seo\" thresholds', () => {\n      const threshold = {\n        seo: 10,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(true);\n    });\n\n    it('should pass if the target categories indexes are above the \"performance\" thresholds', () => {\n      const threshold = {\n        performance: 70,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(true);\n    });\n\n    it('should pass if the target categories indexes are above the \"pwa\" thresholds', () => {\n      const threshold = {\n        pwa: 90,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(true);\n    });\n\n    it('should pass if the target categories indexes are above the \"accessibility\" thresholds', () => {\n      const threshold = {\n        accessibility: 10,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(true);\n    });\n\n    it('should fail if the target categories indexes are above the \"best-practices\" thresholds', () => {\n      const threshold = {\n        'best-practices': 11,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(false);\n    });\n\n    it('should fail if the target categories indexes are above the \"seo\" thresholds', () => {\n      const threshold = {\n        seo: 11,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(false);\n    });\n\n    it('should fail if the target categories indexes are above the \"performance\" thresholds', () => {\n      const threshold = {\n        performance: 71,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(false);\n    });\n\n    it('should fail if the target categories indexes are above the \"pwa\" thresholds', () => {\n      const threshold = {\n        pwa: 91,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(false);\n    });\n\n    it('should fail if the target categories indexes are above the \"accessibility\" thresholds', () => {\n      const threshold = {\n        accessibility: 11,\n      };\n\n      const result = analyzeScore(mockCategoryReport, threshold);\n\n      expect(result).toEqual(false);\n    });\n  });\n\n  it('should return `false` if one or more target category index is below the thresholds', () => {\n    const threshold = {\n      performance: 70,\n      pwa: 80,\n    };\n    const mockCategoryReport = {\n      performance: 70,\n      pwa: 79,\n      accessibility: 10,\n      'best-practices': 10,\n      seo: 10,\n    };\n\n    const result = analyzeScore(mockCategoryReport, threshold);\n\n    expect(result).toEqual(false);\n  });\n});\n"
  },
  {
    "path": "bin/cli.js",
    "content": "#!/usr/bin/env node\n\n/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst fs = require('fs');\nconst path = require('path');\nconst meow = require('meow');\nconst ora = require('ora');\nconst chalk = require('chalk');\nconst updateNotifier = require('update-notifier');\nconst pkg = require('../package.json');\n\nconst { getChromeFlags } = require('../lib/config');\nconst lighthouseReporter = require('../lib/lighthouse-reporter');\nconst { calculateResults } = require('../lib/calculate-results');\n\nconst spinner = ora({\n  color: 'yellow',\n});\n\nconst cli = meow(\n  `\n  Usage\n    $ lighthouse-ci <target-url>\n\n  Example\n    $ lighthouse-ci https://example.com\n    $ lighthouse-ci https://example.com -s\n    $ lighthouse-ci https://example.com --score=75\n    $ lighthouse-ci https://example.com --accessibility=90 --seo=80\n    $ lighthouse-ci https://example.com --accessibility=90 --seo=80 --report=folder\n    $ lighthouse-ci https://example.com -report=folder --config-path=configs.json\n\n  Options\n    -s, --silent                    Run Lighthouse without printing report log\n    --report=<path>                 Generate an HTML report inside a specified folder\n    --filename=<filename>           Specify the name of the generated HTML report file (requires --report)\n    -json, --jsonReport             Generate JSON report in addition to HTML (requires --report)\n    --config-path                   The path to the Lighthouse config JSON (read more here: https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md)\n    --budget-path                   The path to the Lighthouse budgets config JSON (read more here: https://developers.google.com/web/tools/lighthouse/audits/budgets)\n    --score=<threshold>             Specify a score threshold for the CI to pass\n    --performance=<threshold>       Specify a minimal performance score for the CI to pass\n    --pwa=<threshold>               Specify a minimal pwa score for the CI to pass\n    --accessibility=<threshold>     Specify a minimal accessibility score for the CI to pass\n    --best-practice=<threshold>     [DEPRECATED] Use best-practices instead\n    --best-practices=<threshold>    Specify a minimal best-practice score for the CI to pass\n    --seo=<threshold>               Specify a minimal seo score for the CI to pass\n    --fail-on-budgets               Specify CI should fail if budgets are exceeded\n    --budget.<counts|sizes>.<type>  Specify individual budget threshold (if --budget-path not set)\n\n  In addition to listed \"lighthouse-ci\" configuration flags, it is also possible to pass any native \"lighthouse\" flag\n  To see the full list of available flags, please refer to the official Google Lighthouse documentation at https://github.com/GoogleChrome/lighthouse#cli-options\n`,\n  {\n    flags: {\n      report: {\n        type: 'string',\n      },\n      filename: {\n        type: 'string',\n        alias: 'f',\n        default: 'report.html',\n      },\n      jsonReport: {\n        type: 'boolean',\n        alias: 'json',\n        default: false,\n      },\n      silent: {\n        type: 'boolean',\n        alias: 's',\n        default: false,\n      },\n      score: {\n        type: 'string',\n      },\n      performance: {\n        type: 'string',\n      },\n      pwa: {\n        type: 'string',\n      },\n      accessibility: {\n        type: 'string',\n      },\n      bestPractice: {\n        type: 'string',\n      },\n      bestPractices: {\n        type: 'string',\n      },\n      seo: {\n        type: 'string',\n      },\n      failOnBudgets: {\n        type: 'boolean',\n        default: false,\n      },\n      budget: {\n        type: 'string',\n      },\n    },\n  },\n);\n\nconst {\n  report,\n  filename,\n  json,\n  jsonReport,\n  silent,\n  s,\n  score,\n  performance,\n  pwa,\n  accessibility,\n  bestPractice,\n  bestPractices,\n  seo,\n  failOnBudgets,\n  ...lighthouseFlags\n} = cli.flags;\n\nconst calculatedBestPractices = bestPractice || bestPractices;\nconst flags = {\n  report,\n  filename,\n  jsonReport: json || jsonReport,\n  silent,\n  s,\n  score,\n  performance,\n  pwa,\n  accessibility,\n  ...(calculatedBestPractices && {\n    'best-practices': calculatedBestPractices,\n  }),\n  seo,\n  failOnBudgets,\n};\n\nasync function init(args, chromeFlags) {\n  const testUrl = args[0];\n\n  // Run Google Lighthouse\n  const { categoryReport, budgetsReport, htmlReport, jsonReport } = await lighthouseReporter(\n    testUrl,\n    flags,\n    chromeFlags,\n    lighthouseFlags,\n  );\n  const { silent } = flags;\n\n  if (flags.report) {\n    const outputPath = path.resolve(flags.report, flags.filename);\n    await fs.writeFileSync(outputPath, htmlReport);\n\n    if (flags.jsonReport && jsonReport) {\n      const jsonReportPath = outputPath.replace(/\\.[^.]+$/, '.json');\n      await fs.writeFileSync(jsonReportPath, jsonReport);\n    }\n  }\n\n  return {\n    categoryReport,\n    budgetsReport,\n    silent,\n  };\n}\n\nPromise.resolve()\n  .then(() => {\n    updateNotifier({ pkg }).notify();\n\n    if (cli.input.length === 0) {\n      return cli.showHelp();\n    }\n\n    spinner.text = `Running Lighthouse on ${cli.input} ...\\n`;\n    spinner.start();\n\n    return init(cli.input, getChromeFlags());\n  })\n  .then(({ categoryReport, budgetsReport, silent }) => {\n    spinner.stop();\n\n    if (!silent) {\n      for (const category in categoryReport) {\n        if (typeof categoryReport[category] === 'undefined') {\n          continue;\n        }\n\n        console.log(`${chalk.yellow(category)}: ${chalk.yellow(categoryReport[category])}`);\n      }\n\n      for (const budget in budgetsReport) {\n        if (!budgetsReport[budget]) {\n          continue;\n        }\n\n        if (budgetsReport[budget]) {\n          console.log(`Budget '${chalk.yellow(budget)}' exceeded by ${chalk.yellow(budgetsReport[budget])}`);\n        }\n      }\n    }\n\n    return { categoryReport, budgetsReport };\n  })\n  .then(({ categoryReport, budgetsReport }) => {\n    const result = calculateResults(flags, categoryReport, budgetsReport, failOnBudgets);\n\n    if (result.passed) {\n      console.log(chalk.green('\\nAll checks are passing. 🎉\\n'));\n      return process.exit(0);\n    }\n\n    console.log(chalk.red('\\nFailed. ❌'));\n\n    if (result.score === false) {\n      throw new Error('Target score not reached.');\n    }\n\n    if (result.budget === false) {\n      throw new Error('Target budget not reached.');\n    }\n\n    throw new Error('lighthouse-ci test failed.');\n  })\n  .catch((error) => {\n    spinner.stop();\n    console.log(chalk.red(error), '\\n');\n    return process.exit(1);\n  });\n"
  },
  {
    "path": "demo/.dockerignore",
    "content": "node_modules\n*.log"
  },
  {
    "path": "demo/.npmrc",
    "content": "package-lock=false\n"
  },
  {
    "path": "demo/Dockerfile",
    "content": "FROM node:10\n\nWORKDIR /demo\n\nENV CHROME_BIN=/usr/bin/google-chrome-stable\nEXPOSE 8080\n\n# Install Chrome\nRUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -\nRUN sh -c 'echo \"deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'\nRUN apt-get update && apt-get install -y google-chrome-stable\n\nCOPY package.json .\n\nRUN npm install\n\nCOPY . .\n"
  },
  {
    "path": "demo/Makefile",
    "content": ".PHONY: demo build run dev\n\nbuild:\n\tdocker build --rm -t sonny/lighthouse-demo .\n\nrun:\n\tdocker run --rm sonny/lighthouse-demo npm start\n\ndev:\n\tdocker run -it --rm -v ${PWD}:/demo sonny/lighthouse-demo /bin/bash\n\ndemo: build run\n"
  },
  {
    "path": "demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n<body>\n  <h1>Allo!</h1>\n</body>\n</html>\n"
  },
  {
    "path": "demo/package.json",
    "content": "{\n  \"name\": \"demo-project\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"serve\": \"http-server\",\n    \"lighthouse\": \"lighthouse-ci http://127.0.0.1:8080 --report\",\n    \"start\": \"concurrently -r -s first -k \\\"npm run serve\\\" \\\"npm run lighthouse\\\"\"\n  },\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"concurrently\": \"^5.1.0\",\n    \"http-server\": \"^0.12.1\",\n    \"lighthouse-ci\": \"^1.10.0\"\n  }\n}\n"
  },
  {
    "path": "lib/budgets-analyzer.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nfunction analyzeBudgets(budgetsReport, failOnBudgets) {\n  if (!failOnBudgets) {\n    return true;\n  }\n\n  const budgetLength = Object.keys(budgetsReport || {}).length;\n\n  return budgetLength === 0;\n}\n\nmodule.exports = analyzeBudgets;\n"
  },
  {
    "path": "lib/calculate-results.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst { scoreReducer } = require('./helpers');\nconst analyzeScore = require('./score-analyzer');\nconst analyzeBudgets = require('./budgets-analyzer');\nconst { getScores } = require('./config');\n\nconst calculateResults = (flags, categoryReport, budgetsReport, failOnBudgets) => {\n  let thresholds = scoreReducer(flags, getScores());\n  thresholds =\n    Object.keys(thresholds).length === 0\n      ? {\n          score: 100,\n        }\n      : thresholds;\n\n  if (thresholds && Object.keys(thresholds).length > 0) {\n    const isScorePassing = analyzeScore(categoryReport, thresholds);\n    const areBudgetsPassing = analyzeBudgets(budgetsReport, failOnBudgets);\n\n    if (isScorePassing && areBudgetsPassing) {\n      return {\n        passed: true,\n      };\n    }\n\n    return {\n      passed: false,\n      score: isScorePassing,\n      budget: areBudgetsPassing,\n    };\n  }\n\n  return {\n    passed: false,\n  };\n};\n\nmodule.exports = {\n  calculateResults,\n};\n"
  },
  {
    "path": "lib/config.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst scores = ['performance', 'pwa', 'accessibility', 'best-practices', 'seo'];\nconst chromeFlags = ['--disable-gpu', '--headless', '--no-zygote', '--no-sandbox'];\n\nconst getScores = () => scores;\nconst getChromeFlags = () => chromeFlags;\n\nmodule.exports = { getScores, getChromeFlags };\n"
  },
  {
    "path": "lib/helpers.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst mkdirp = require('mkdirp');\nconst rimraf = require('rimraf');\n\nconst clean = () =>\n  new Promise((resolve, reject) => {\n    rimraf('./lighthouse/', (err) => {\n      if (err) {\n        return reject(err);\n      }\n\n      return resolve();\n    });\n  });\n\nconst createDir = () =>\n  new Promise((resolve, reject) => {\n    mkdirp('./lighthouse', (err) => {\n      if (err) {\n        return reject(err);\n      }\n\n      return resolve();\n    });\n  });\n\nconst scoreReducer = (flags, scoreList) => {\n  if (flags.score) {\n    return flags.score;\n  }\n\n  return scoreList.reduce((scores, flag) => {\n    if (!Object.prototype.hasOwnProperty.call(flags, flag)) {\n      return scores;\n    }\n\n    return {\n      ...scores,\n      [flag]: flags[flag],\n    };\n  }, {});\n};\n\nconst createDefaultConfig = (config) => {\n  if (!config) {\n    config = {\n      extends: 'lighthouse:default',\n      settings: {},\n    };\n  }\n\n  if (!config.settings) {\n    config.settings = {};\n  }\n\n  return config;\n};\n\nconst getOwnProps = (object) => {\n  return Object.keys(object).filter((key) => Object.prototype.hasOwnProperty.call(object, key));\n};\n\nconst convertToBudgetList = (object) => {\n  return getOwnProps(object)\n    .filter((key) => object[key])\n    .reduce((acc, key) => {\n      acc.push({\n        resourceType: key,\n        budget: object[key],\n      });\n      return acc;\n    }, []);\n};\n\nconst convertToResourceKey = (key) => 'resource' + key.charAt(0).toUpperCase() + key.slice(1);\n\nmodule.exports = {\n  clean,\n  createDir,\n  scoreReducer,\n  createDefaultConfig,\n  getOwnProps,\n  convertToBudgetList,\n  convertToResourceKey,\n};\n"
  },
  {
    "path": "lib/lighthouse-reporter.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nconst fs = require('fs');\nconst path = require('path');\nconst { promisify } = require('util');\nconst lighthouse = require('lighthouse');\nconst chromeLauncher = require('chrome-launcher');\nconst ReportGenerator = require('lighthouse/report/generator/report-generator');\nconst chalk = require('chalk');\nconst { createDefaultConfig, getOwnProps, convertToBudgetList, convertToResourceKey } = require('./helpers');\n\nconst readFile = promisify(fs.readFile);\n\nconst launchChromeAndRunLighthouse = async (url, chromeFlags, lighthouseFlags, configPath, budgetPath) => {\n  const chrome = await chromeLauncher.launch({\n    chromeFlags,\n  });\n  const flags = {\n    port: chrome.port,\n    output: 'json',\n    ...lighthouseFlags,\n  };\n  let config;\n\n  if (flags.extraHeaders) {\n    let extraHeadersString = flags.extraHeaders;\n    if (extraHeadersString.slice(0, 1) !== '{') {\n      extraHeadersString = await readFile(extraHeadersString, 'UTF-8');\n    }\n\n    flags.extraHeaders = JSON.parse(extraHeadersString);\n  }\n\n  if (configPath) {\n    try {\n      const configJson = await readFile(path.resolve(configPath), 'UTF-8');\n      config = JSON.parse(configJson);\n    } catch (error) {\n      throw new Error(error.message);\n    }\n  }\n\n  if (budgetPath) {\n    try {\n      const budgetJson = await readFile(path.resolve(budgetPath), 'UTF-8');\n\n      config = createDefaultConfig(config);\n\n      config.settings.budgets = JSON.parse(budgetJson);\n    } catch (error) {\n      throw new Error(error.message);\n    }\n  } else if (flags && flags.budget) {\n    config = createDefaultConfig(config);\n\n    const { budget } = flags;\n\n    const budgetConfigs = getOwnProps(budget)\n      .filter((key) => budget[key] && (key === 'counts' || key === 'sizes'))\n      .reduce(\n        (acc, key) => {\n          acc[convertToResourceKey(key)] = convertToBudgetList(budget[key]);\n          return acc;\n        },\n        {\n          resourceSizes: [],\n          resourceCounts: [],\n        },\n      );\n\n    config.settings.budgets = [budgetConfigs];\n  }\n\n  const result = await lighthouse(url, flags, config);\n  await chrome.kill();\n\n  if (!result || !result.lhr) {\n    throw new Error('Something went wrong when running Lighthouse against the given url');\n  }\n\n  if (result.lhr.runtimeError) {\n    throw new Error(result.lhr.runtimeError.message);\n  }\n\n  if (result.lhr.runWarnings.length > 0) {\n    for (const warningMessage of result.lhr.runWarnings) {\n      console.warn(`\\n${chalk.yellow('WARNING:')} ${warningMessage}`);\n    }\n\n    console.warn('\\n');\n  }\n\n  return result;\n};\n\nconst createHtmlReport = (results, flags) => {\n  if (flags.report) {\n    return ReportGenerator.generateReportHtml(results);\n  }\n\n  return null;\n};\n\nconst createJsonReport = (results, flags) => {\n  if (flags.report && flags.jsonReport) {\n    return ReportGenerator.generateReport(results, 'json');\n  }\n\n  return null;\n};\n\nconst createCategoryReport = (results) => {\n  const { categories } = results;\n\n  return getOwnProps(categories).reduce((categoryReport, categoryName) => {\n    const category = results.categories[categoryName];\n    categoryReport[category.id] = Math.round(category.score * 100);\n    return categoryReport;\n  }, {});\n};\n\nconst createBudgetsReport = (results) => {\n  const items =\n    (results.audits &&\n      results.audits['performance-budget'] &&\n      results.audits['performance-budget'].details &&\n      results.audits['performance-budget'].details.items) ||\n    [];\n\n  const timings =\n    (results.audits &&\n      results.audits['timing-budget'] &&\n      results.audits['timing-budget'].details &&\n      results.audits['timing-budget'].details.items) ||\n    [];\n\n  const report = items.reduce((acc, object) => {\n    if (object.countOverBudget) {\n      acc[object.resourceType + '-count'] = `${object.countOverBudget}`;\n    }\n\n    if (object.sizeOverBudget) {\n      acc[object.resourceType + '-size'] = `${Math.round(object.sizeOverBudget / 1024, 0)}kb`;\n    }\n\n    return acc;\n  }, {});\n\n  const timingReport = timings.reduce((acc, { overBudget, metric }) => {\n    if (overBudget && typeof overBudget === 'object') {\n      const { value } = overBudget;\n      if (value && typeof value === 'number') {\n        acc[metric] = value;\n      }\n    }\n\n    if (overBudget && typeof overBudget === 'number') {\n      acc[metric] = `${overBudget}ms`;\n    }\n\n    return acc;\n  }, {});\n\n  return { ...report, ...timingReport };\n};\n\nasync function writeReport(url, flags = {}, defaultChromeFlags = [], lighthouseFlags = {}) {\n  const { chromeFlags, configPath, budgetPath, ...extraLHFlags } = lighthouseFlags;\n  const customChromeFlags = chromeFlags ? chromeFlags.split(',') : [];\n\n  const lighthouseResult = await launchChromeAndRunLighthouse(\n    url,\n    [...defaultChromeFlags, ...customChromeFlags],\n    extraLHFlags,\n    configPath,\n    budgetPath,\n  );\n\n  const htmlReport = createHtmlReport(lighthouseResult.lhr, flags);\n  const jsonReport = createJsonReport(lighthouseResult.lhr, flags);\n\n  const categoryReport = createCategoryReport(lighthouseResult.lhr);\n  const budgetsReport = createBudgetsReport(lighthouseResult.lhr);\n\n  return { categoryReport, budgetsReport, htmlReport, jsonReport };\n}\n\nmodule.exports = writeReport;\n"
  },
  {
    "path": "lib/score-analyzer.js",
    "content": "/**\n *  Copyright (c) 2018-2021 AndreaSonny <andreasonny83@gmail.com> (https://github.com/andreasonny83)\n *\n * This software is released under the MIT License.\n * https://opensource.org/licenses/MIT\n */\n\nfunction analyzeScores(thresholds, categoryReport) {\n  for (const category in categoryReport) {\n    if (!Object.prototype.hasOwnProperty.call(thresholds, category)) {\n      continue;\n    }\n\n    if (Number(categoryReport[category]) < Number(thresholds[category])) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nfunction analyzeTotalScore(threshold, categoryReport) {\n  for (const category in categoryReport) {\n    if (Number(categoryReport[category]) < Number(threshold)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nfunction analyzeScore(categoryReport, thresholds) {\n  if (!thresholds || thresholds.length === 0) {\n    throw new Error('Invalid threshold score.');\n  }\n\n  return typeof thresholds === 'object'\n    ? analyzeScores(thresholds, categoryReport)\n    : analyzeTotalScore(thresholds, categoryReport);\n}\n\nmodule.exports = analyzeScore;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"lighthouse-ci\",\n  \"version\": \"1.13.1\",\n  \"description\": \"CLI implementation for running Lighthouse with any CI tool\",\n  \"scripts\": {\n    \"test\": \"npm run format && xo && jest --detectOpenHandles\",\n    \"xo\": \"xo\",\n    \"test:watch\": \"jest --watchAll --detectOpenHandles\",\n    \"format\": \"prettier --write \\\"**/*.{js,js}\\\"\",\n    \"contributors:add\": \"all-contributors add\",\n    \"contributors:generate\": \"all-contributors generate\",\n    \"release\": \"np\",\n    \"snyk-protect\": \"snyk protect\",\n    \"prepare\": \"npm run snyk-protect\"\n  },\n  \"type\": \"commonjs\",\n  \"dependencies\": {\n    \"chrome-launcher\": \"^0.14.0\",\n    \"lighthouse\": \"^8.4.0\",\n    \"meow\": \"^9.0.0\",\n    \"mkdirp\": \"^1.0.4\",\n    \"ora\": \"^5.4.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"update-notifier\": \"^5.1.0\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^26.0.24\",\n    \"@types/ora\": \"^3.2.0\",\n    \"all-contributors-cli\": \"^6.20.0\",\n    \"chalk\": \"^4.1.1\",\n    \"jest\": \"^27.0.6\",\n    \"np\": \"^7.5.0\",\n    \"prettier\": \"^2.3.2\",\n    \"snyk\": \"^1.660.0\",\n    \"xo\": \"~0.36.0\"\n  },\n  \"keywords\": [\n    \"devtools\",\n    \"lighthouse\",\n    \"ci\"\n  ],\n  \"bin\": {\n    \"lighthouse-ci\": \"bin/cli.js\"\n  },\n  \"files\": [\n    \"lib\",\n    \"bin\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"engines\": {\n    \"node\": \"^12.20.0 || ^14.13.1 || >=16.0.0\"\n  },\n  \"xo\": {\n    \"prettier\": true,\n    \"envs\": [\n      \"node\",\n      \"es6\",\n      \"jest\"\n    ],\n    \"rules\": {\n      \"max-params\": [\n        \"error\",\n        5\n      ],\n      \"unicorn/no-reduce\": 0\n    }\n  },\n  \"author\": \"Andrea Sonny <andreasonny83@gmail.com>\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/andreasonny83/lighthouse-ci.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/andreasonny83/lighthouse-ci.git/issues\"\n  },\n  \"homepage\": \"https://github.com/andreasonny83/lighthouse-ci.git#readme\",\n  \"snyk\": true\n}\n"
  }
]