sitespeed.io/bin/sitespeed.js

201 lines
6.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
/*eslint no-console: 0*/
import { writeFileSync } from 'node:fs';
import { execSync } from 'node:child_process';
import { platform } from 'node:os';
import path from 'node:path';
import { readFileSync } from 'node:fs';
import { EventEmitter } from 'node:events';
import merge from 'lodash.merge';
import ora from 'ora';
import { parseCommandLine } from '../lib/cli/cli.js';
import { run } from '../lib/sitespeed.js';
import { addTest, waitAndGetResult, get } from '../lib/api/send.js';
// This is due to CDP do no have (or has it) a way to remove listeners
// and default 10 is quite small number.
EventEmitter.defaultMaxListeners = 30;
async function api(options) {
const action = options.api.action ?? 'addAndGetResult';
if (action === 'get' && !options.api.id) {
process.exitCode = 1;
console.log('Missing test id --api.id');
process.exit();
}
const hostname = options.api.hostname;
let apiOptions = options.explicitOptions;
// Delete the hostname to make sure the server do not end in
// a forever loop
delete apiOptions.api.hostname;
// Add support for running multi tests
if (options.multi) {
const scripting = await readFileSync(
new URL(path.resolve(process.cwd(), options._[0]), import.meta.url)
);
apiOptions.api.scripting = scripting.toString();
apiOptions.api.scriptingName = path.basename(options._[0]);
}
if (apiOptions.mobile) {
apiOptions.api.testType = 'emulatedMobile';
} else if (apiOptions.android) {
apiOptions.api.testType = 'android';
} else if (apiOptions.safari && apiOptions.safari.ios) {
apiOptions.api.testType = 'ios';
} else {
apiOptions.api.testType = 'desktop';
}
if (options.config) {
const config = JSON.parse(
await readFileSync(
new URL(path.resolve(process.cwd(), options.config), import.meta.url)
)
);
apiOptions = merge(options.explicitOptions, config);
delete apiOptions.config;
delete apiOptions.extends;
}
// We copy all browsertime settings to fix the problem when we use --config
// and then try to ovverride some configurations using command line
apiOptions.browsertime = options.browsertime;
if (action === 'add' || action === 'addAndGetResult') {
const spinner = ora({
text: `Send test to ${hostname} testing ${options._[0]}`,
isSilent: options.api.silent
}).start();
try {
const data = await addTest(hostname, apiOptions);
const testId = JSON.parse(data).id;
spinner.color = 'yellow';
spinner.text = `Added test ${options._[0]} with id ${testId}`;
if (action === 'add') {
spinner.succeed(`Added test ${options._[0]} with id ${testId}`);
console.log(testId);
process.exit();
} else if (action === 'addAndGetResult') {
const result = await waitAndGetResult(
testId,
hostname,
apiOptions,
spinner
);
if (result.status === 'completed') {
spinner.succeed(`Got test result with id ${testId}`);
if (options.api.json) {
console.log(JSON.stringify(result));
} else {
console.log(result.result);
}
} else if (result.status === 'failed') {
spinner.fail('Test failed');
if (options.api.json) {
console.log(JSON.stringify(result));
}
process.exitCode = 1;
process.exit();
}
}
} catch (error) {
spinner.fail(error.message);
process.exitCode = 1;
process.exit();
}
} else if (action === 'get') {
try {
const result = await get(options.api.id, hostname, apiOptions);
if (options.api.json) {
console.log(JSON.stringify(result));
} else {
console.log(result);
}
} catch (error) {
process.exitCode = 1;
console.log(error);
}
}
}
async function start() {
let parsed = await parseCommandLine();
// hack for getting in the unchanged cli options
parsed.options.explicitOptions = parsed.explicitOptions;
parsed.options.urls = parsed.urls;
parsed.options.urlsMetaData = parsed.urlsMetaData;
let options = parsed.options;
if (options.api && options.api.hostname && !options.disableAPI) {
api(options);
} else {
try {
const result = await run(options);
// This can be used as an option to get hold of where the data is stored
// for third parties
if (options.storeResult) {
if (options.storeResult == 'true') {
writeFileSync('result.json', JSON.stringify(result));
} else {
// Use the name supplied
writeFileSync(options.storeResult, JSON.stringify(result));
}
}
if ((options.open || options.o) && platform() === 'darwin') {
execSync('open ' + result.localPath + '/index.html');
} else if ((options.open || options.o) && platform() === 'linux') {
execSync('xdg-open ' + result.localPath + '/index.html');
}
// If one of the URLs is marked as a failure in Browsertime
// then set the exit code
for (let bt of result.browsertime) {
if (bt.markedAsFailure === 1) {
process.exitCode = 1;
}
}
// If we have a failing budget, change the exit code
if (
parsed.options.budget &&
Object.keys(result.budgetResult.failing).length > 0
) {
process.exitCode = 1;
}
// If you supress the exit code using budgets, we will always return 0
if (parsed.options.budget && parsed.options.budget.suppressExitCode) {
process.exitCode = 0;
}
// If the exit code is still not set (it can be set in the scripting also)
// Make sure we exit with a 0
if (process.exitCode === undefined) {
process.exitCode = 0;
}
if (result.errors.length > 0) {
throw new Error('Errors while running:\n' + result.errors.join('\n'));
}
} catch {
process.exitCode = 1;
} finally {
process.exit();
}
}
}
await start();