Skip to content

feat: allow passing extra headers to Lighthouse #182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Empty file added functions/echo.js
Empty file.
4 changes: 4 additions & 0 deletions manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
46 changes: 28 additions & 18 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
};
});
Expand Down
29 changes: 29 additions & 0 deletions src/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand All @@ -91,6 +92,7 @@ describe('config', () => {
url: 'url',
thresholds: { seo: 1 },
output_path: 'reports/lighthouse.html',
extra_headers: { Authorization: 'test' },
},
],
});
Expand Down Expand Up @@ -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: {},
},
],
});
});
});
21 changes: 18 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/lighthouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down