@bolt/build-tools
Version:
Curated collection of front-end build tools in the Bolt Design System.
274 lines (231 loc) • 8.25 kB
JavaScript
const chalk = require('chalk');
const path = require('path');
const { promisify } = require('util');
const fs = require('fs');
const writeFile = promisify(fs.writeFile);
const chokidar = require('chokidar');
const del = require('del');
const debounce = require('lodash.debounce');
const Ora = require('ora');
const log = require('@bolt/build-utils/log');
const { getConfig } = require('@bolt/build-utils/config-store');
const events = require('@bolt/build-utils/events');
const sh = require('@bolt/build-utils/sh');
const { readYamlFileSync } = require('@bolt/build-utils/yaml');
const manifest = require('@bolt/build-utils/manifest');
const timer = require('@bolt/build-utils/timer');
const { fileExists, dirExists } = require('@bolt/build-utils/general');
const shell = require('shelljs');
let plSource, plPublic, consolePath;
let config;
let initialBuild = true;
let isWatching = false;
async function asyncConfig() {
if (config) {
return config;
} else {
config = Object.assign(
{
plConfigFile: 'config/config.yml',
watchedExtensions: ['twig', 'json', 'yaml', 'yml', 'md'],
debounceRate: 1000,
},
await getConfig(),
);
const plConfig = readYamlFileSync(config.plConfigFile);
const plRoot = path.join(config.plConfigFile, '../..');
plSource = path.join(plRoot, plConfig.sourceDir);
plPublic = path.join(plRoot, plConfig.publicDir);
consolePath = path.join(plRoot, 'core/console');
return config;
}
}
async function compile(errorShouldExit, dataOnly = false) {
config = config || (await asyncConfig());
const plTaskName = dataOnly ? 'Pattern Lab Data' : 'Pattern Lab';
return new Promise(async (resolve, reject) => {
const startCompilingPlMsg = `Building ${plTaskName} for the first time...`;
const startRecompilingPlMsg = `Recompiling ${plTaskName}...`;
const failedCompilingPlMsg = `The initial ${plTaskName} compile failed!`;
const failedRecompilingPlMsg = `Failed to recompile ${plTaskName}!`;
const endCompilingPlMsg = function(startTime) {
return `Compiled ${plTaskName} in ${chalk.bold(timer.end(startTime))}`;
};
const endRecompilingPlMsg = function(startTime) {
return `${plTaskName} ${
initialBuild ? 'compiled' : 'recompiled'
} in ${chalk.bold(timer.end(startTime))}`;
};
const plSpinner = new Ora(
chalk.blue(initialBuild ? startCompilingPlMsg : startRecompilingPlMsg),
).start();
const startTime = timer.start();
sh(
'php',
[
'-d',
'memory_limit=4048M',
consolePath,
'--generate',
dataOnly ? '--dataonly' : '',
],
errorShouldExit,
false,
)
.then(output => {
plSpinner.succeed(
chalk.green(
initialBuild
? endCompilingPlMsg(startTime)
: endRecompilingPlMsg(startTime),
),
);
if (!dataOnly) {
initialBuild = false;
}
if (config.verbosity > 2) {
console.log('---');
console.log(output);
console.log('===\n');
}
if (dataOnly) {
events.emit('build-tasks/pattern-lab:compiled-data');
} else {
events.emit('build-tasks/pattern-lab:compiled');
}
// events.emit('reload');
resolve(output);
})
.catch(error => {
plSpinner.fail(
chalk.red(
initialBuild ? failedCompilingPlMsg : failedRecompilingPlMsg,
),
);
if (!dataOnly) {
initialBuild = false;
}
console.log(error);
reject(error);
});
});
}
async function precompile() {
config = config || (await asyncConfig());
return new Promise(async (resolve, reject) => {
const jsFolderExists = await dirExists(
path.join(process.cwd(), config.wwwDir, 'pattern-lab/styleguide/js/'),
);
const scssFolderExists = await dirExists(
path.join(process.cwd(), config.wwwDir, 'pattern-lab/styleguide/css/'),
);
const indexHtmlExists = await fileExists(
path.join(process.cwd(), config.wwwDir, 'pattern-lab/index.html'),
);
const isPatternLabAlreadyCompiled =
jsFolderExists && scssFolderExists && indexHtmlExists;
await compile(true)
.then(output => {
// check if pattern lab's UIKIt assets exist -- automatically regenerate if the required assets are missing.
if (!isPatternLabAlreadyCompiled || config.prod === true) {
chalk.yellow(
'⚠️ Uh-oh. Pattern Labs UIKit is missing... Regenerating!',
);
const result = shell.exec(
`yarn --cwd ${path.join(
process.cwd(),
'../packages/website-ui/uikit-workshop',
)} run build`,
).stdout;
resolve(result);
} else {
resolve();
}
})
.catch(error => {
console.log(error);
reject(error);
});
});
}
compile.description = 'Compile Pattern Lab';
compile.displayName = 'pattern-lab:compile';
async function compileWithNoExit() {
await compile(true);
}
compileWithNoExit.displayName = 'pattern-lab:compile';
async function watch() {
config = config || (await asyncConfig());
const dirs = await manifest.getAllDirs();
// Used by watches
const debouncedCompile = debounce(compileWithNoExit, config.debounceRate);
const globPattern = `**/*.{${config.watchedExtensions.join(',')}}`;
const watchedFiles = [
dirs.map(dir => path.join(dir, globPattern)),
path.join(plSource, globPattern),
path.join(config.dataDir, '**/*'),
`!${path.join(config.dataDir, 'sassdoc.bolt.json')}`,
`!${path.join(config.dataDir, 'vrt-urls.bolt.json')}`, // ignore to prevent PL from regenerating when this file is output
`!${dirs.map(dir => path.join(dir, '**/*.schema.yml'))}`, // ignore watching schema files since the new schema file watcher below handles this
];
// watch schema files for changes; regenerate the globally shared manifest data when updated
//
// @todo: include other data sources (like package.json) that get pulled into the global data store
// @todo: update manifest.writeBoltManifest to only rewrite files on the filesystem when the data has ACTUALLY changed (avoid unnecessary recompiles)
// @todo: move this entire
const schemaFileWatcher = chokidar.watch(
[
dirs.map(dir => path.join(dir, `**/*.schema.yml`)),
path.join(plSource, `**/*.schema.yml`),
`!${path.join(config.dataDir, 'sassdoc.bolt.json')}`,
],
{
ignoreInitial: true,
cwd: process.cwd(),
ignored: ['**/node_modules/**', '**/vendor/**'],
},
);
schemaFileWatcher.on('all', async (event, path) => {
await manifest.writeBoltManifest();
});
// <!-- @todo: move this entire watch task to a separate top-level data task in the build tools (which would probably include build + watch functions)
// right now this ^ only works when PL is running. this should run independently of PL + the Static site!
let compileWhenReady = false;
// listen for api prep work to complete before re-generating PL
events.on('api-tasks/status-board:generated', async () => {
if (isWatching === true) {
await compileWithNoExit();
} else {
compileWhenReady = true;
}
});
// auto-regenerate when pattern lab data emitted
events.on('webpack-dev-server:compiled', async () => {
if (isWatching === false) {
isWatching = true;
}
if (compileWhenReady) {
compileWhenReady = false;
await compileWithNoExit();
}
const watcher = chokidar.watch(watchedFiles, {
ignoreInitial: true,
cwd: process.cwd(),
ignored: ['**/node_modules/**', '**/vendor/**'],
});
// list of all events: https://www.npmjs.com/package/chokidar#methods--events
watcher.on('all', (event, path) => {
if (config.verbosity > 3) {
console.log('Pattern Lab watch event: ', event, path);
}
debouncedCompile();
});
});
}
watch.description = 'Watch and rebuild Pattern Lab';
watch.displayName = 'pattern-lab:watch';
module.exports = {
compile,
precompile,
watch,
};