From e01f90647c921ac2b96d45d3279e8befac25f8d2 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Thu, 22 Apr 2021 13:25:32 +0400 Subject: [PATCH] feat: allow passing extra headers to Lighthouse --- README.md | 10 ++++++++++ functions/echo.js | 0 manifest.yml | 4 ++++ netlify.toml | 3 +++ src/config.js | 46 ++++++++++++++++++++++++++++------------------ src/config.test.js | 29 +++++++++++++++++++++++++++++ src/index.js | 21 ++++++++++++++++++--- src/lighthouse.js | 3 ++- 8 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 functions/echo.js diff --git a/README.md b/README.md index 6605ac26..8f262ccf 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ Then add the plugin to your `netlify.toml` configuration file: # optional, deploy the lighthouse report to a path under your site [plugins.inputs] output_path = "reports/lighthouse.html" + + # optional, extra headers to pass to Lighthouse. Useful for auditing pages that require authentication + [plugins.inputs.extra_headers] + # IMPORTANT - Don't put secrets in your git committed `netlify.toml` file + # See here https://docs.netlify.com/configure-builds/file-based-configuration/#inject-environment-variable-values on injecting env variables + Authorization = "token" ``` By default, the plugin will serve and audit the build directory of the site. @@ -58,6 +64,10 @@ You can customize the behavior via the `audits` input: # you can specify thresholds per audit [plugins.inputs.audits.thresholds] performance = 0.8 + + # you can specify extra_headers per audit + [plugins.inputs.audits.extra_headers] + CUSTOM_HEADER = "custom value" ``` ## Running Locally diff --git a/functions/echo.js b/functions/echo.js new file mode 100644 index 00000000..e69de29b diff --git a/manifest.yml b/manifest.yml index 6a61b780..1ffd3eef 100644 --- a/manifest.yml +++ b/manifest.yml @@ -13,6 +13,10 @@ inputs: required: false description: Path to save the generated HTML Lighthouse report + - name: extra_headers + required: false + description: Extra headers to pass to Lighthouse. Useful for auditing pages that require authentication + - name: audits required: false description: A list of audits to perform. Each list item is an object with either a url/path to scan and an optional thresholds mapping. diff --git a/netlify.toml b/netlify.toml index fed8e952..579f7870 100644 --- a/netlify.toml +++ b/netlify.toml @@ -11,6 +11,9 @@ package = "./src/index.js" [plugins.inputs] output_path = "reports/lighthouse.html" +[plugins.inputs.extra_headers] +Authorization = "token" + [plugins.inputs.thresholds] performance = 0.9 diff --git a/src/config.js b/src/config.js index 30ed026e..a42ba4d7 100644 --- a/src/config.js +++ b/src/config.js @@ -20,6 +20,18 @@ const getServePath = (dir, path) => { return { path: resolvedPath }; }; +const maybeParseJSON = ({ value, name }) => { + if (typeof value !== 'string') { + return value; + } + + try { + return JSON.parse(value); + } catch (e) { + throw new Error(`Invalid JSON for '${name}' input: ${e.message}`); + } +}; + const getConfiguration = ({ constants, inputs } = {}) => { const serveDir = (constants && constants.PUBLISH_DIR) || process.env.PUBLISH_DIR; @@ -36,34 +48,32 @@ const getConfiguration = ({ constants, inputs } = {}) => { ); } - let thresholds = - (inputs && inputs.thresholds) || process.env.THRESHOLDS || {}; + const thresholds = maybeParseJSON({ + value: (inputs && inputs.thresholds) || process.env.THRESHOLDS || {}, + name: 'thresholds', + }); - if (typeof thresholds === 'string') { - try { - thresholds = JSON.parse(thresholds); - } catch (e) { - throw new Error(`Invalid JSON for 'thresholds' input: ${e.message}`); - } - } + const extra_headers = maybeParseJSON({ + value: (inputs && inputs.extra_headers) || process.env.EXTRA_HEADERS, + name: 'extra_headers', + }); - let audits = (inputs && inputs.audits) || process.env.AUDITS; - if (typeof audits === 'string') { - try { - audits = JSON.parse(audits); - } catch (e) { - throw new Error(`Invalid JSON for 'audits' input: ${e.message}`); - } - } + let audits = maybeParseJSON({ + value: (inputs && inputs.audits) || process.env.AUDITS, + name: 'audits', + }); if (!Array.isArray(audits)) { - audits = [{ path: serveDir, url: auditUrl, thresholds, output_path }]; + audits = [ + { path: serveDir, url: auditUrl, thresholds, output_path, extra_headers }, + ]; } else { audits = audits.map((a) => { return { ...a, thresholds: a.thresholds || thresholds, output_path: a.output_path || output_path, + extra_headers: a.extra_headers || extra_headers, ...getServePath(serveDir, a.path), }; }); diff --git a/src/config.test.js b/src/config.test.js index c383110c..712fbbc6 100644 --- a/src/config.test.js +++ b/src/config.test.js @@ -81,6 +81,7 @@ describe('config', () => { audit_url: 'url', thresholds: { seo: 1 }, output_path: 'reports/lighthouse.html', + extra_headers: { Authorization: 'test' }, }; const config = getConfiguration({ constants, inputs }); @@ -91,6 +92,7 @@ describe('config', () => { url: 'url', thresholds: { seo: 1 }, output_path: 'reports/lighthouse.html', + extra_headers: { Authorization: 'test' }, }, ], }); @@ -203,4 +205,31 @@ describe('config', () => { ], }); }); + + it('should use specific audit extra_headers when configured', () => { + const constants = { PUBLISH_DIR: 'PUBLISH_DIR' }; + const inputs = { + extra_headers: { Authorization: 'authorization' }, + audits: [ + { path: 'route1' }, + { path: 'route2', extra_headers: { Other: 'other' } }, + ], + }; + const config = getConfiguration({ constants, inputs }); + + expect(config).toEqual({ + audits: [ + { + path: 'PUBLISH_DIR/route1', + extra_headers: { Authorization: 'authorization' }, + thresholds: {}, + }, + { + path: 'PUBLISH_DIR/route2', + extra_headers: { Other: 'other' }, + thresholds: {}, + }, + ], + }); + }); }); diff --git a/src/index.js b/src/index.js index 439d5d49..f070e73c 100644 --- a/src/index.js +++ b/src/index.js @@ -120,14 +120,22 @@ const getUtils = ({ utils }) => { return { failBuild, show }; }; -const runAudit = async ({ path, url, thresholds, output_path }) => { +const runAudit = async ({ + path, + url, + thresholds, + output_path, + extra_headers, +}) => { try { const { server } = getServer({ serveDir: path, auditUrl: url }); const browserPath = await getBrowserPath(); const { error, results } = await new Promise((resolve) => { server.listen(async () => { try { - const results = await runLighthouse(browserPath, server.url); + const results = await runLighthouse(browserPath, server.url, { + extraHeaders: extra_headers, + }); resolve({ error: false, results }); } catch (error) { resolve({ error }); @@ -229,12 +237,19 @@ module.exports = { const allErrors = []; const summaries = []; - for (const { path, url, thresholds, output_path } of audits) { + for (const { + path, + url, + thresholds, + output_path, + extra_headers, + } of audits) { const { errors, summary, shortSummary } = await runAudit({ path, url, thresholds, output_path, + extra_headers, }); if (summary) { console.log(summary); diff --git a/src/lighthouse.js b/src/lighthouse.js index f6b1f9cf..6277179f 100644 --- a/src/lighthouse.js +++ b/src/lighthouse.js @@ -30,7 +30,7 @@ const getBrowserPath = async () => { return info.executablePath; }; -const runLighthouse = async (browserPath, url) => { +const runLighthouse = async (browserPath, url, options) => { let chrome; try { const logLevel = 'info'; @@ -49,6 +49,7 @@ const runLighthouse = async (browserPath, url) => { port: chrome.port, output: 'html', logLevel, + ...options, }); if (results.lhr.runtimeError) { throw new Error(results.lhr.runtimeError.message);