beam-cli
Version:
A beautifully simple CLI for running Lighthouse audits on a statically generated (SSG) website
181 lines (180 loc) • 6.8 kB
JavaScript
import fsPromises from 'node:fs/promises';
import enquirerPkg from 'enquirer';
import ora from 'ora';
import chalk from 'chalk';
import { errorMessage, printLines, warningMessage } from '../console/print.js';
import { defaultOptions } from '../options/defaults.js';
import { saveJSONFile } from '../fs/json.js';
import { ensureFileExistence } from '../fs/file.js';
const { prompt } = enquirerPkg;
/**
* Gets the list of folders within the current working directory
* @returns string array of folder paths
*/
const getListOfFolders = async () => {
const filesAndFolders = await fsPromises.readdir('./', {
withFileTypes: true,
});
const folders = filesAndFolders
.filter(item => item.isDirectory())
.map(item => item.name);
return folders;
};
const askYesNo = async (message, initial) => {
const response = await prompt({
type: 'select',
name: 'answer',
message,
choices: initial ? ['Yes', 'No'] : ['No', 'Yes'],
});
return response.answer === 'Yes';
};
const appendToGitIgnore = async (gitIgnoreFilePath, content) => {
const spinner = ora({
text: 'Updating .gitignore',
color: 'cyan',
});
spinner.start();
try {
let gitIgnoreContents = await fsPromises.readFile(gitIgnoreFilePath, 'utf8');
gitIgnoreContents += content;
await fsPromises.writeFile(gitIgnoreFilePath, gitIgnoreContents, 'utf8');
spinner.succeed('Updated .gitignore');
}
catch (error) {
spinner.fail('Unable to edit gitignore');
if (error instanceof Error) {
errorMessage(error.message);
}
}
};
export const setupWizard = async () => {
const configuration = {};
printLines([
'This setup utility will walk you through creating a configuration file.',
'It only covers the most common items, and tries to guess sensible defaults.',
'',
'Press ^C at any time to quit.',
'',
]);
const folders = await getListOfFolders();
const customFolderString = '[other...]';
folders.push(customFolderString);
const { folder } = await prompt({
type: 'select',
name: 'folder',
message: 'Please select your build directory:',
choices: folders,
});
let selectedFolder = folder;
if (folder === customFolderString) {
const { dist } = await prompt({
type: 'input',
name: 'dist',
message: 'Please enter the relative path to your build directory:',
initial: defaultOptions.dist,
validate: (ans) => ans.length > 0 ? true : 'Please enter a value',
});
selectedFolder = dist;
}
configuration.dist = selectedFolder;
const presets = await prompt({
type: 'multiselect',
name: 'presets',
message: 'Please select which Lighthouse presets to run:',
initial: 0,
choices: [
{
name: 'mobile',
message: 'Mobile',
hint: 'Simulate a Mobile Device (Phone)',
},
{
name: 'desktop',
message: 'Desktop',
hint: 'Simulate a Desktop Computer',
},
],
validate: ans => ans.length > 0 ? true : 'Please select at least one preset',
});
const all = await askYesNo('Would you like to run all the audit categories provided by Lighthouse?', true);
let categoriesAnswer = all
? 'all'
: ['performance'];
if (!all) {
const categories = await prompt({
type: 'multiselect',
name: 'categories',
message: 'Please select which Lighthouse categories to run:',
initial: 0,
choices: [
{ message: 'Performance', name: 'performance', hint: '' },
{ message: 'Best Practices', name: 'best-practices', hint: '' },
{ message: 'Accessibility', name: 'accessibility', hint: 'A11y' },
{ message: 'SEO', name: 'seo', hint: 'Search Engine Optimisation' },
{ message: 'PWA', name: 'pwa', hint: 'Progressive Web App' },
],
validate: ans => ans.length > 0 ? true : 'Please select at least one category',
});
categoriesAnswer = categories.categories;
}
configuration.lighthouse = {
mobile: presets.presets.includes('mobile'),
desktop: presets.presets.includes('desktop'),
categories: categoriesAnswer,
};
const html = await askYesNo('Would you like keep HTML reports generated by Lighthouse:', defaultOptions.output.html);
const json = await askYesNo('Would you like keep JSON reports generated by Lighthouse:', defaultOptions.output.json);
configuration.output = { html, json };
if (html || json) {
const { destination } = await prompt({
type: 'input',
name: 'destination',
message: 'Please enter the destination folder for storing the reports:',
initial: defaultOptions.output.folder,
validate: (ans) => ans.length > 0 ? true : 'Please enter a value',
});
configuration.output.folder = destination;
const gitIgnoreFilePath = '.gitignore';
const gitIgnoreExists = ensureFileExistence(gitIgnoreFilePath);
if (gitIgnoreExists) {
const editGitIgnore = await askYesNo('Would you like to add this folder to your .gitignore?', false);
if (editGitIgnore) {
await appendToGitIgnore(gitIgnoreFilePath, `\n# Beam Results Folder\n${destination}\n`);
}
}
}
printLines([
'',
'This setup utility will now create and save the configuration file.',
]);
warningMessage('Note: this will override any file with a matching name.');
console.log('');
const { fileName } = await prompt({
type: 'input',
name: 'fileName',
message: 'Please enter a file name for the configuration file:',
initial: '.beam.json',
validate(ans) {
if (ans.length === 0)
return 'Please enter a value';
if (ans.length < 6 || !ans.endsWith('.json')) {
return 'File should end with the `.json` file extension';
}
return true;
},
});
const spinner = ora({
text: 'Saving configuration file',
color: 'cyan',
});
spinner.start();
await saveJSONFile(fileName, configuration);
spinner.succeed('Configuration file saved');
printLines([
'',
'You can now run Beam with the following command:',
`$ ${chalk.cyan(`beam ${fileName === '.beam.json' ? '' : `-c ${fileName}`}`)}`,
'',
]);
};