sitespeed.io 35.7.5 → 36.0.0-alpha.2
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/.dockerignore +4 -1
- package/CHANGELOG.md +52 -0
- package/Dockerfile +9 -5
- package/Dockerfile-slim +9 -7
- package/LICENSE +1 -1
- package/bin/browsertimeWebPageReplay.js +1 -1
- package/lib/cli/cli.js +17 -25
- package/lib/core/logging.js +6 -98
- package/lib/core/queue.js +112 -0
- package/lib/core/queueHandler.js +20 -28
- package/lib/core/resultsStorage/pathToFolder.js +3 -3
- package/lib/core/resultsStorage/storageManager.js +7 -5
- package/lib/plugins/assets/index.js +1 -1
- package/lib/plugins/axe/axePostScript.cjs +4 -6
- package/lib/plugins/axe/index.js +2 -2
- package/lib/plugins/browsertime/analyzer.js +6 -7
- package/lib/plugins/browsertime/browsertimeAggregator.js +64 -42
- package/lib/plugins/browsertime/filmstrip.js +2 -2
- package/lib/plugins/browsertime/index.js +3 -3
- package/lib/plugins/budget/deprecatedVerify.js +2 -2
- package/lib/plugins/budget/index.js +2 -2
- package/lib/plugins/budget/json.js +2 -2
- package/lib/plugins/budget/junit.js +2 -2
- package/lib/plugins/budget/tap.js +2 -2
- package/lib/plugins/budget/verify.js +2 -2
- package/lib/plugins/coach/aggregator.js +7 -9
- package/lib/plugins/coach/index.js +2 -2
- package/lib/plugins/compare/helper.js +2 -2
- package/lib/plugins/compare/index.js +2 -2
- package/lib/plugins/crawler/index.js +2 -2
- package/lib/plugins/crux/index.js +2 -2
- package/lib/plugins/crux/send.js +2 -2
- package/lib/plugins/domains/aggregator.js +9 -11
- package/lib/plugins/domains/index.js +1 -1
- package/lib/plugins/gcs/index.js +17 -16
- package/lib/plugins/grafana/send-annotation.js +2 -2
- package/lib/plugins/graphite/data-generator.js +13 -17
- package/lib/plugins/graphite/index.js +4 -3
- package/lib/plugins/graphite/send-annotation.js +2 -2
- package/lib/plugins/graphite/sender.js +2 -2
- package/lib/plugins/html/dataCollector.js +8 -14
- package/lib/plugins/html/htmlBuilder.js +12 -10
- package/lib/plugins/html/index.js +2 -5
- package/lib/plugins/html/renderer.js +2 -2
- package/lib/plugins/html/setup/summaryBoxes.js +2 -2
- package/lib/plugins/html/templates/url/coach/technology.pug +10 -8
- package/lib/plugins/influxdb/data-generator.js +6 -8
- package/lib/plugins/influxdb/index.js +3 -4
- package/lib/plugins/influxdb/send-annotation.js +2 -2
- package/lib/plugins/influxdb/send-annotationV2.js +2 -2
- package/lib/plugins/lateststorer/index.js +1 -4
- package/lib/plugins/matrix/index.js +2 -2
- package/lib/plugins/matrix/send.js +2 -2
- package/lib/plugins/messagelogger/index.js +4 -3
- package/lib/plugins/pagexray/index.js +2 -2
- package/lib/plugins/pagexray/pagexrayAggregator.js +11 -10
- package/lib/plugins/remove/index.js +2 -2
- package/lib/plugins/s3/index.js +29 -28
- package/lib/plugins/s3/limit.js +34 -0
- package/lib/plugins/scp/index.js +6 -10
- package/lib/plugins/slack/index.js +20 -21
- package/lib/plugins/sustainable/index.js +35 -14
- package/lib/plugins/text/color.js +30 -0
- package/lib/plugins/text/textBuilder.js +50 -20
- package/lib/sitespeed.js +11 -15
- package/lib/support/fileUtil.js +40 -0
- package/lib/support/filterRegistry.js +4 -4
- package/lib/support/flattenMessage.js +2 -2
- package/lib/support/messageMaker.js +2 -2
- package/lib/support/metricsFilter.js +9 -16
- package/lib/support/osUtil.js +36 -0
- package/lib/support/util.js +24 -0
- package/npm-shrinkwrap.json +1098 -2593
- package/package.json +13 -31
- package/tools/postinstall.js +61 -0
- package/.github/CONTRIBUTING.md +0 -24
- package/.github/FUNDING.yml +0 -12
- package/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +0 -65
- package/.github/ISSUE_TEMPLATE/FEATURE_IMPROVEMENT.yml +0 -15
- package/.github/ISSUE_TEMPLATE/QUESTION.yml +0 -15
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -14
- package/.github/budget.json +0 -13
- package/.github/workflows/building-docker-autobuild.yml +0 -30
- package/.github/workflows/building-docker-release.yml +0 -76
- package/.github/workflows/crux-test.yml +0 -23
- package/.github/workflows/docker-scan.yml +0 -29
- package/.github/workflows/docker.yml +0 -39
- package/.github/workflows/linux.yml +0 -80
- package/.github/workflows/safari.yml +0 -30
- package/.github/workflows/sitespeed-io-action-example.yml +0 -22
- package/.github/workflows/unittests.yml +0 -41
- package/.github/workflows/windows.yml +0 -39
- package/.github/workflows/windowsFull.yml +0 -36
- package/.netlify +0 -1
- package/.spelling +0 -58
- package/Gemfile +0 -4
- package/Gemfile.lock +0 -53
- package/lib/plugins/sustainable/data/url2green.json.gz +0 -0
- package/release/feed.js +0 -198
- package/release/friendlyNames.js +0 -9
- package/release/friendlyNamesBudget.js +0 -15
- package/wpr-record.log +0 -102
- package/wpr-replay.log +0 -96
package/lib/plugins/scp/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import fs from 'node:fs';
|
|
|
3
3
|
|
|
4
4
|
import { SitespeedioPlugin } from '@sitespeed.io/plugin';
|
|
5
5
|
import { Client } from 'node-scp';
|
|
6
|
-
import
|
|
7
|
-
import intel from 'intel';
|
|
6
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
8
7
|
import { throwIfMissing } from '../../support/util.js';
|
|
8
|
+
import { recursiveReaddir } from '../../support/fileUtil.js';
|
|
9
9
|
|
|
10
|
-
const log =
|
|
10
|
+
const log = getLogger('sitespeedio.plugin.scp');
|
|
11
11
|
|
|
12
12
|
async function getClient(scpOptions) {
|
|
13
13
|
const options = {
|
|
@@ -46,7 +46,7 @@ async function upload(dir, scpOptions, prefix) {
|
|
|
46
46
|
}
|
|
47
47
|
await client.uploadDir(dir, path.join(scpOptions.destinationPath, prefix));
|
|
48
48
|
} catch (error) {
|
|
49
|
-
log.error(error);
|
|
49
|
+
log.error(`Error uploading dir ` + error);
|
|
50
50
|
throw error;
|
|
51
51
|
} finally {
|
|
52
52
|
if (client) {
|
|
@@ -66,7 +66,7 @@ async function uploadFiles(files, scpOptions, prefix) {
|
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
} catch (error) {
|
|
69
|
-
log.error(error);
|
|
69
|
+
log.error(`Error uploading files ` + error);
|
|
70
70
|
throw error;
|
|
71
71
|
} finally {
|
|
72
72
|
if (client) {
|
|
@@ -75,12 +75,8 @@ async function uploadFiles(files, scpOptions, prefix) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
function ignoreDirectories(file, stats) {
|
|
79
|
-
return stats.isDirectory();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
78
|
async function uploadLatestFiles(dir, scpOptions, prefix) {
|
|
83
|
-
const files = await
|
|
79
|
+
const files = await recursiveReaddir(dir, true);
|
|
84
80
|
|
|
85
81
|
return uploadFiles(files, scpOptions, prefix);
|
|
86
82
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import intel from 'intel';
|
|
4
|
-
import Slack from 'node-slack';
|
|
1
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
2
|
+
import { IncomingWebhook } from '@slack/webhook';
|
|
5
3
|
import merge from 'lodash.merge';
|
|
6
4
|
import set from 'lodash.set';
|
|
7
5
|
import { SitespeedioPlugin } from '@sitespeed.io/plugin';
|
|
@@ -11,7 +9,7 @@ import { getAttachements } from './attachements.js';
|
|
|
11
9
|
import { getSummary } from './summary.js';
|
|
12
10
|
import { throwIfMissing } from '../../support/util.js';
|
|
13
11
|
|
|
14
|
-
const log =
|
|
12
|
+
const log = getLogger('sitespeedio.plugin.slack');
|
|
15
13
|
|
|
16
14
|
const defaultConfig = {
|
|
17
15
|
userName: 'Sitespeed.io',
|
|
@@ -23,8 +21,7 @@ const defaultConfig = {
|
|
|
23
21
|
|
|
24
22
|
function send(options, dataCollector, context, screenshotType, alias) {
|
|
25
23
|
const slackOptions = merge({}, defaultConfig, options.slack);
|
|
26
|
-
const slack = new
|
|
27
|
-
const send = promisify(slack.send.bind(slack));
|
|
24
|
+
const slack = new IncomingWebhook(slackOptions.hookUrl);
|
|
28
25
|
const type = slackOptions.type;
|
|
29
26
|
const pageErrors = [];
|
|
30
27
|
let logo = 'https://www.sitespeed.io/img/slack/sitespeed-logo-slack.png';
|
|
@@ -73,20 +70,22 @@ function send(options, dataCollector, context, screenshotType, alias) {
|
|
|
73
70
|
slackOptions.channel,
|
|
74
71
|
slackOptions.userName
|
|
75
72
|
);
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
return slack
|
|
74
|
+
.send({
|
|
75
|
+
text,
|
|
76
|
+
icon_url: logo,
|
|
77
|
+
channel,
|
|
78
|
+
mrkdwn: true,
|
|
79
|
+
username: slackOptions.userName,
|
|
80
|
+
attachments
|
|
81
|
+
})
|
|
82
|
+
.catch(error => {
|
|
83
|
+
if (error.errno === 'ETIMEDOUT') {
|
|
84
|
+
log.warn('Timeout sending Slack message.');
|
|
85
|
+
} else {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
|
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import zlib from 'node:zlib';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
8
8
|
import { co2, hosting } from '@tgwf/co2';
|
|
9
9
|
import { SitespeedioPlugin } from '@sitespeed.io/plugin';
|
|
10
10
|
import { Aggregator } from './aggregator.js';
|
|
@@ -27,7 +27,7 @@ const packageJson = JSON.parse(
|
|
|
27
27
|
const co2Version = packageJson.dependencies['@tgwf/co2'];
|
|
28
28
|
|
|
29
29
|
const gunzip = promisify(zlib.gunzip);
|
|
30
|
-
const log =
|
|
30
|
+
const log = getLogger('sitespeedio.plugin.sustainable');
|
|
31
31
|
|
|
32
32
|
const DEFAULT_METRICS_PAGE_SUMMARY = [
|
|
33
33
|
'co2PerPageView',
|
|
@@ -79,7 +79,30 @@ export default class SustainablePlugin extends SitespeedioPlugin {
|
|
|
79
79
|
DEFAULT_METRICS_PAGE_SUMMARY,
|
|
80
80
|
'sustainable.pageSummary'
|
|
81
81
|
);
|
|
82
|
+
|
|
83
|
+
const greenDomainJSONpath = path.join(
|
|
84
|
+
__dirname,
|
|
85
|
+
'data',
|
|
86
|
+
'url2green.json.gz'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (
|
|
90
|
+
!this.sustainableOptions.disableHosting &&
|
|
91
|
+
!this.sustainableOptions.useGreenWebHostingAPI
|
|
92
|
+
) {
|
|
93
|
+
log.info('Reading green domain data from disk');
|
|
94
|
+
try {
|
|
95
|
+
this.data = await loadJSON(greenDomainJSONpath);
|
|
96
|
+
} catch {
|
|
97
|
+
log.error('Green domain local file is missing on disk.');
|
|
98
|
+
log.error(
|
|
99
|
+
'Run `DOWNLOAD_URL2GREEN=true npm install sitespeed.io` when you install to install the file locally or use the online API using `--sustainable.useGreenWebHostingAPI true` to check if a domain is green hosted.'
|
|
100
|
+
);
|
|
101
|
+
this.data = {};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
82
104
|
}
|
|
105
|
+
|
|
83
106
|
async processMessage(message, queue) {
|
|
84
107
|
const make = this.make;
|
|
85
108
|
const aggregator = this.aggregator;
|
|
@@ -118,23 +141,21 @@ export default class SustainablePlugin extends SitespeedioPlugin {
|
|
|
118
141
|
case 'pagexray.run': {
|
|
119
142
|
// We got data for a URL, lets calculate co2, check green servers etc
|
|
120
143
|
const listOfDomains = Object.keys(message.data.domains);
|
|
121
|
-
const greenDomainJSONpath = path.join(
|
|
122
|
-
__dirname,
|
|
123
|
-
'data',
|
|
124
|
-
'url2green.json.gz'
|
|
125
|
-
);
|
|
126
144
|
|
|
127
145
|
let hostingGreenCheck;
|
|
128
146
|
if (this.sustainableOptions.disableHosting === true) {
|
|
129
147
|
hostingGreenCheck = [];
|
|
130
148
|
} else {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
if (this.sustainableOptions.useGreenWebHostingAPI === true) {
|
|
150
|
+
log.info('Fetch hosting information from The Green Web Foundation');
|
|
151
|
+
hostingGreenCheck = await hosting(
|
|
152
|
+
listOfDomains,
|
|
153
|
+
undefined,
|
|
154
|
+
'sitespeed.io'
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
hostingGreenCheck = await hosting(listOfDomains, this.data);
|
|
158
|
+
}
|
|
138
159
|
}
|
|
139
160
|
|
|
140
161
|
const configuration = { model: this.sustainableOptions.model };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const ansi = {
|
|
2
|
+
reset: '\u001B[0m',
|
|
3
|
+
bold: '\u001B[1m',
|
|
4
|
+
green: '\u001B[32m',
|
|
5
|
+
yellow: '\u001B[33m',
|
|
6
|
+
red: '\u001B[31m',
|
|
7
|
+
blackBright: '\u001B[90m'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function stripAnsi(str) {
|
|
11
|
+
// eslint-disable-next-line no-control-regex
|
|
12
|
+
return str.replaceAll(/\u001B\[[0-9;]*[A-Za-z]/g, '');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function colorise(str, clr) {
|
|
16
|
+
if (!clr || !ansi[clr]) return str;
|
|
17
|
+
return ansi[clr] + str + ansi.reset;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function makeBold(str) {
|
|
21
|
+
return ansi.bold + str + ansi.reset;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getStrippedLength(str) {
|
|
25
|
+
return stripAnsi(str).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function blackBright(str) {
|
|
29
|
+
return colorise(str, 'blackBright');
|
|
30
|
+
}
|
|
@@ -1,16 +1,42 @@
|
|
|
1
|
-
import table from 'text-table';
|
|
2
|
-
import flatten from 'lodash.flatten';
|
|
3
|
-
import clic from 'cli-color';
|
|
4
|
-
import chunk from 'lodash.chunk';
|
|
5
|
-
|
|
6
1
|
import { plural, short, cap } from '../../support/helpers/index.js';
|
|
7
2
|
import summaryBoxesSetup from '../html/setup/summaryBoxes.js';
|
|
3
|
+
import { colorise, makeBold, getStrippedLength, blackBright } from './color.js';
|
|
8
4
|
|
|
9
|
-
const { getStrippedLength, blackBright, bold } = clic;
|
|
10
5
|
const tableOptions = {
|
|
11
6
|
stringLength: getStrippedLength
|
|
12
7
|
};
|
|
13
|
-
|
|
8
|
+
|
|
9
|
+
function table(rows, options = {}) {
|
|
10
|
+
const { stringLength = str => str.length } = options;
|
|
11
|
+
|
|
12
|
+
const colWidths = [];
|
|
13
|
+
for (const row of rows) {
|
|
14
|
+
for (const [i, cell] of row.entries()) {
|
|
15
|
+
const len = stringLength(String(cell));
|
|
16
|
+
colWidths[i] = Math.max(colWidths[i] || 0, len);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return rows
|
|
21
|
+
.map(row =>
|
|
22
|
+
row
|
|
23
|
+
.map((cell, i) => {
|
|
24
|
+
const cellStr = String(cell);
|
|
25
|
+
const padding = colWidths[i] - stringLength(cellStr);
|
|
26
|
+
return cellStr + ' '.repeat(padding);
|
|
27
|
+
})
|
|
28
|
+
.join(' ')
|
|
29
|
+
)
|
|
30
|
+
.join('\n');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function chunk(array, size) {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (let i = 0; i < array.length; i += size) {
|
|
36
|
+
out.push(array.slice(i, i + size));
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
14
40
|
|
|
15
41
|
function getMarker(label) {
|
|
16
42
|
return { ok: '√', warning: '!', error: '✗', info: '' }[label] || '';
|
|
@@ -25,22 +51,21 @@ function getColor(label) {
|
|
|
25
51
|
|
|
26
52
|
function getHeader(context, options) {
|
|
27
53
|
const noPages = options.urls.length;
|
|
28
|
-
return
|
|
54
|
+
return blackBright(
|
|
29
55
|
[
|
|
30
56
|
`${plural(noPages, 'page')} analysed for ${short(context.name, 30)} `,
|
|
31
57
|
`(${plural(options.browsertime.iterations, 'run')}, `,
|
|
32
58
|
`${cap(options.browsertime.browser)}/${
|
|
33
59
|
options.mobile ? 'mobile' : 'desktop'
|
|
34
|
-
}
|
|
60
|
+
})`
|
|
35
61
|
].join('')
|
|
36
62
|
);
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
function getBoxes(metrics, html) {
|
|
40
|
-
return
|
|
66
|
+
return chunk(summaryBoxesSetup(metrics, html).filter(Boolean), 3).flat();
|
|
41
67
|
}
|
|
42
68
|
|
|
43
|
-
// foo bar -> fb
|
|
44
69
|
function abbr(string_) {
|
|
45
70
|
if (/total|overall/i.test(string_)) return string_.trim();
|
|
46
71
|
return string_.replaceAll(/\w+\s?/g, a => a[0]);
|
|
@@ -54,9 +79,9 @@ export function renderSummary(metrics, context, options) {
|
|
|
54
79
|
let out = getHeader(context, options);
|
|
55
80
|
let rows = getBoxes(metrics, options.html).map(b => {
|
|
56
81
|
const marker = getMarker(b.label),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return [
|
|
82
|
+
clr = getColor(b.label);
|
|
83
|
+
|
|
84
|
+
return [colorise(marker, clr), colorise(b.name, clr), makeBold(b.median)];
|
|
60
85
|
});
|
|
61
86
|
rows.unshift(
|
|
62
87
|
['', 'Score / Metric', 'Median'],
|
|
@@ -72,13 +97,13 @@ export function renderBriefSummary(metrics, context, options) {
|
|
|
72
97
|
reqs = '',
|
|
73
98
|
rum = '';
|
|
74
99
|
getBoxes(metrics, options.html).map(b => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
100
|
+
const clr = getColor(b.label);
|
|
101
|
+
let value = b.median;
|
|
102
|
+
let name;
|
|
103
|
+
const colorFn = clr ? str => colorise(str, clr) : noop;
|
|
79
104
|
if (/score$/i.test(b.url)) {
|
|
80
105
|
name = abbr(b.name.replace('score', ''));
|
|
81
|
-
scores.push(
|
|
106
|
+
scores.push(colorFn(`${name}:${value}`));
|
|
82
107
|
} else if ('pageSize' === b.url) {
|
|
83
108
|
value = value.replace(' ', ''); // 10 KB -> 10KB
|
|
84
109
|
size = `${value}`;
|
|
@@ -86,6 +111,11 @@ export function renderBriefSummary(metrics, context, options) {
|
|
|
86
111
|
reqs = `${value} reqs`;
|
|
87
112
|
}
|
|
88
113
|
});
|
|
89
|
-
lines.push(
|
|
114
|
+
lines.push(
|
|
115
|
+
blackBright('Score: ') + scores.reverse().join(', '),
|
|
116
|
+
size,
|
|
117
|
+
reqs,
|
|
118
|
+
rum
|
|
119
|
+
);
|
|
90
120
|
options.summary = { out: `${out}\n` + lines.join(' / ') };
|
|
91
121
|
}
|
package/lib/sitespeed.js
CHANGED
|
@@ -6,9 +6,7 @@ import fs from 'node:fs/promises';
|
|
|
6
6
|
|
|
7
7
|
import dayjs from 'dayjs';
|
|
8
8
|
import utc from 'dayjs/plugin/utc.js';
|
|
9
|
-
import
|
|
10
|
-
import pullAll from 'lodash.pullall';
|
|
11
|
-
import union from 'lodash.union';
|
|
9
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
12
10
|
|
|
13
11
|
import { configure } from './core/logging.js';
|
|
14
12
|
import { toArray } from './support/util.js';
|
|
@@ -26,7 +24,7 @@ const packageJson = JSON.parse(
|
|
|
26
24
|
await fs.readFile(path.resolve(path.join(__dirname, '..', 'package.json')))
|
|
27
25
|
);
|
|
28
26
|
|
|
29
|
-
const log =
|
|
27
|
+
const log = getLogger('sitespeedio');
|
|
30
28
|
dayjs.extend(utc);
|
|
31
29
|
|
|
32
30
|
const budgetResult = {
|
|
@@ -61,9 +59,6 @@ export async function run(options) {
|
|
|
61
59
|
options
|
|
62
60
|
);
|
|
63
61
|
|
|
64
|
-
// Setup logging
|
|
65
|
-
const logDir = await storageManager.createDirectory('logs');
|
|
66
|
-
|
|
67
62
|
if (
|
|
68
63
|
options.browsertime &&
|
|
69
64
|
options.browsertime.tcpdump &&
|
|
@@ -74,7 +69,7 @@ export async function run(options) {
|
|
|
74
69
|
'SSLKEYLOGFILE.txt'
|
|
75
70
|
);
|
|
76
71
|
}
|
|
77
|
-
configure(options
|
|
72
|
+
configure(options);
|
|
78
73
|
|
|
79
74
|
// Tell the world what we are using
|
|
80
75
|
log.info(
|
|
@@ -85,9 +80,8 @@ export async function run(options) {
|
|
|
85
80
|
packageJson.dependencies.browsertime,
|
|
86
81
|
packageJson.dependencies['coach-core']
|
|
87
82
|
);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
83
|
+
|
|
84
|
+
log.debug('Running with options: %:2j', options);
|
|
91
85
|
|
|
92
86
|
let pluginNames = await parsePluginNames(options);
|
|
93
87
|
|
|
@@ -107,8 +101,11 @@ export async function run(options) {
|
|
|
107
101
|
// Finalize the plugins that we wanna run
|
|
108
102
|
// First we add the new ones and then remove, that means remove
|
|
109
103
|
// always wins
|
|
110
|
-
pluginNames =
|
|
111
|
-
|
|
104
|
+
pluginNames = [...new Set([...pluginNames, ...toArray(plugins.add)])];
|
|
105
|
+
|
|
106
|
+
const removeSet = new Set(toArray(plugins.remove));
|
|
107
|
+
pluginNames = pluginNames.filter(name => !removeSet.has(name));
|
|
108
|
+
|
|
112
109
|
if (plugins.list) {
|
|
113
110
|
log.info('The following plugins are enabled: %s', pluginNames.join(', '));
|
|
114
111
|
}
|
|
@@ -122,12 +119,11 @@ export async function run(options) {
|
|
|
122
119
|
budget: budgetResult,
|
|
123
120
|
name: url,
|
|
124
121
|
log,
|
|
125
|
-
|
|
122
|
+
getLogger,
|
|
126
123
|
messageMaker,
|
|
127
124
|
statsHelpers,
|
|
128
125
|
filterRegistry
|
|
129
126
|
};
|
|
130
|
-
|
|
131
127
|
const queueHandler = new QueueHandler(options);
|
|
132
128
|
const runningPlugins = await loadPlugins(
|
|
133
129
|
pluginNames,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export async function recursiveReaddir(dir, ignoreDirectories = false) {
|
|
6
|
+
const results = [];
|
|
7
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
8
|
+
|
|
9
|
+
for (const entry of entries) {
|
|
10
|
+
const fullPath = path.join(dir, entry.name);
|
|
11
|
+
|
|
12
|
+
if (entry.isDirectory()) {
|
|
13
|
+
if (!ignoreDirectories) {
|
|
14
|
+
results.push(fullPath);
|
|
15
|
+
}
|
|
16
|
+
const subPaths = await recursiveReaddir(fullPath, ignoreDirectories);
|
|
17
|
+
results.push(...subPaths);
|
|
18
|
+
} else {
|
|
19
|
+
results.push(fullPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function findUpSync(filenames, startDir = process.cwd()) {
|
|
27
|
+
let currentDir = startDir;
|
|
28
|
+
|
|
29
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
30
|
+
for (const filename of filenames) {
|
|
31
|
+
const filePath = path.resolve(currentDir, filename);
|
|
32
|
+
if (existsSync(filePath)) {
|
|
33
|
+
return filePath;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
currentDir = path.dirname(currentDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import clone from 'lodash.clonedeep';
|
|
2
1
|
import { filterMetrics } from './metricsFilter.js';
|
|
3
2
|
|
|
4
3
|
let filterForType = {};
|
|
@@ -34,7 +33,8 @@ export function filterMessage(message) {
|
|
|
34
33
|
return message;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
return {
|
|
37
|
+
...message,
|
|
38
|
+
data: filterMetrics(message.data, filterConfig)
|
|
39
|
+
};
|
|
40
40
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse } from 'node:url';
|
|
2
|
-
import
|
|
3
|
-
const log =
|
|
2
|
+
import { getLogger } from '@sitespeed.io/log';
|
|
3
|
+
const log = getLogger('sitespeedio');
|
|
4
4
|
|
|
5
5
|
function joinNonEmpty(strings, delimeter) {
|
|
6
6
|
return strings.filter(Boolean).join(delimeter);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
2
|
import merge from 'lodash.merge';
|
|
3
|
-
import {
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
export function messageMaker(source) {
|
|
6
6
|
return {
|
|
7
7
|
make(type, data, extras) {
|
|
8
8
|
const timestamp = dayjs().format();
|
|
9
|
-
const uuid =
|
|
9
|
+
const uuid = randomUUID();
|
|
10
10
|
|
|
11
11
|
return merge({ uuid, type, timestamp, source, data }, extras);
|
|
12
12
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import isEmpty from 'lodash.isempty';
|
|
2
1
|
import get from 'lodash.get';
|
|
3
2
|
import set from 'lodash.set';
|
|
4
3
|
import merge from 'lodash.merge';
|
|
5
|
-
import reduce from 'lodash.reduce';
|
|
6
4
|
|
|
7
|
-
import { toArray } from './util.js';
|
|
5
|
+
import { toArray, isEmpty } from './util.js';
|
|
8
6
|
|
|
9
7
|
function normalizePath(path) {
|
|
10
8
|
if (path.endsWith('.*')) return path.slice(0, -2);
|
|
@@ -31,20 +29,15 @@ export function filterMetrics(json, metricPaths) {
|
|
|
31
29
|
} else if (firstWildcard === 0) {
|
|
32
30
|
const leafPath = path.slice(2);
|
|
33
31
|
|
|
34
|
-
reduce(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!isEmpty(leaf)) {
|
|
41
|
-
result[key] = leaf;
|
|
42
|
-
}
|
|
32
|
+
Object.entries(json).reduce((result, [key, value]) => {
|
|
33
|
+
if (typeof value === 'object') {
|
|
34
|
+
const leaf = this.filterMetrics(value, leafPath);
|
|
35
|
+
if (leaf && Object.keys(leaf).length > 0) {
|
|
36
|
+
result[key] = leaf;
|
|
43
37
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}, result);
|
|
48
41
|
} else {
|
|
49
42
|
let branchPath = path.slice(0, Math.max(0, firstWildcard));
|
|
50
43
|
if (branchPath.endsWith('.')) branchPath = branchPath.slice(0, -1);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { promises as fsPromises } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export function osName() {
|
|
5
|
+
const platform = os.platform();
|
|
6
|
+
if (platform === 'win32') return 'Windows';
|
|
7
|
+
if (platform === 'darwin') return 'macOS';
|
|
8
|
+
if (platform === 'linux') return 'Linux';
|
|
9
|
+
return platform;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function getOS() {
|
|
13
|
+
if (os.platform() !== 'linux') {
|
|
14
|
+
return { dist: osName(), release: '' };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const data = await fsPromises.readFile('/etc/os-release', 'utf8');
|
|
19
|
+
const lines = data.split('\n');
|
|
20
|
+
let dist = 'Linux';
|
|
21
|
+
let release = '';
|
|
22
|
+
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
if (line.startsWith('NAME=')) {
|
|
25
|
+
dist = line.split('=')[1].replaceAll('"', '');
|
|
26
|
+
} else if (line.startsWith('VERSION_ID=')) {
|
|
27
|
+
release = line.split('=')[1].replaceAll('"', '');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { dist, release };
|
|
32
|
+
} catch {
|
|
33
|
+
// Return fallback values if reading fails
|
|
34
|
+
return { dist: 'Linux', release: '' };
|
|
35
|
+
}
|
|
36
|
+
}
|
package/lib/support/util.js
CHANGED
|
@@ -21,3 +21,27 @@ export function throwIfMissing(options, keys, namespace) {
|
|
|
21
21
|
);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
export function isEmpty(value) {
|
|
26
|
+
if (value === null) return true;
|
|
27
|
+
|
|
28
|
+
if (value === undefined) return true;
|
|
29
|
+
|
|
30
|
+
if (typeof value === 'boolean') return false;
|
|
31
|
+
|
|
32
|
+
if (typeof value === 'number') return false;
|
|
33
|
+
|
|
34
|
+
if (typeof value === 'string') return value.length === 0;
|
|
35
|
+
|
|
36
|
+
if (typeof value === 'function') return false;
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
39
|
+
|
|
40
|
+
if (value instanceof Map || value instanceof Set) return value.size === 0;
|
|
41
|
+
|
|
42
|
+
if (typeof value === 'object') {
|
|
43
|
+
return Object.keys(value).length === 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|