patternplate-server
Version:
Programmatically serve atomic patterns via a REST API
214 lines (176 loc) • 6.73 kB
JavaScript
import assert from 'assert';
import path from 'path';
import {debuglog} from 'util';
import boxen from 'boxen';
import {merge, uniq} from 'lodash';
import {padEnd, padStart} from 'lodash/fp';
import minimatch from 'minimatch';
import ora from 'ora';
import throat from 'throat';
import {loadTransforms} from '../../../library/transforms';
import {normalizeFormats} from '../../../library/pattern';
import copyStatic from '../common/copy-static';
import getEnvironments from '../../../library/utilities/get-environments';
import getPatternMtimes from '../../../library/utilities/get-pattern-mtimes';
import getPatterns from '../../../library/utilities/get-patterns';
import writeSafe from '../../../library/filesystem/write-safe';
const where = `Configure it at configuration/patternplate-server/tasks.js.`;
export default async (application, settings) => {
if (!settings) {
throw new Error('build-bundles is not configured in .tasks');
}
assert(typeof settings.patterns === 'object', `build-commonjs needs a valid patterns configuration. ${where} build-bundles.patterns`);
assert(typeof settings.patterns.formats === 'object', `build-commonjs needs a valid patterns.formats configuration. ${where} build-bundles.patterns.formats`);
assert(typeof settings.transforms === 'object', `build-commonjs needs a valid transforms configuration. ${where} build-bundles.transforms`);
const filterEnvironments = settings.env ? env => settings.env.includes(env.name) : () => true;
const debug = debuglog('bundles');
const spinner = ora().start();
debug('calling bundles with');
debug(settings);
const cwd = process.cwd();
const base = path.resolve(cwd, 'patterns');
const buildBase = path.resolve(cwd, 'build', `build-bundles`);
const {
cache, log, transforms,
pattern: {factory}
} = application;
// Override pattern config
settings.patterns.formats = normalizeFormats(settings.patterns.formats);
application.configuration.patterns = settings.patterns;
// Reinitialize transforms
application.configuration.transforms = settings.transforms || {};
application.transforms = (await loadTransforms(settings.transforms || {}))(application);
const warnings = [];
const warn = application.log.warn;
application.log.warn = (...args) => {
if (args.some(arg => arg.includes('Deprecation'))) {
warnings.push(args);
return;
}
warn(...args);
};
// Get environments
const loadedEnvironments = await getEnvironments(base, {cache, log});
// Environments have to apply on all patterns
const environments = loadedEnvironments
.filter(filterEnvironments)
.map(environment => {
environment.applyTo = '**/*';
return environment;
});
// Get available patterns
const availablePatterns = await getPatternMtimes(base, {
resolveDependencies: true
});
let envCount = 1;
const envs = environments
.filter(environment => environment.include && environment.include.length);
spinner.stop();
const envMaxLength = envs.map(e => e.name.length).reduce((a, b) => a > b ? a : b, 0);
const envMaxPad = padEnd(envMaxLength + 1);
const envCountPad = padStart(String(envs.length).length);
// For each environment with an include key, build a bundle for each enabled format
await Promise.all(envs
.map(throat(1, async environment => {
const {environment: envConfig, include, exclude, formats} = environment;
const includePatterns = include || [];
const excludePatterns = exclude || ['@'];
const envSpinner = ora().start();
const envr = `${envMaxPad(environment.name)} [env: ${envCountPad(envCount)}/${envs.length}]`;
// Get patterns matching the include config
const includedPatterns = availablePatterns.filter(available => {
const {id} = available;
return includePatterns.some(pattern => minimatch(id, pattern)) &&
!excludePatterns.concat('@environments/**/*').some(pattern => minimatch(id, pattern));
});
if (!includedPatterns.length) {
application.log.warn(`No patterns to read for environment ${environment.name}. Check the .includes key of the environment configuration.`);
}
// Merge environment config into transform config
const config = merge(
{},
{
patterns: settings.patterns,
transforms: settings.transforms
},
envConfig,
{
environments: [environment.name]
}
);
const filters = merge({}, settings.filters, {
inFormats: formats,
environments: [environment.name]
});
let read = 0;
const readPad = padStart(String(includedPatterns.length).length);
// build all patterns matching the include config
const readPatterns = await Promise.all(includedPatterns
.map(throat(1, async pattern => {
const {id} = pattern;
envSpinner.text = `${envr}: read [patterns: ${readPad(read)}/${includedPatterns.length}] ${pattern.id}`;
const [result] = await getPatterns({
id,
base,
config,
factory,
transforms,
log,
filters,
environment
}, cache, ['read']);
read += 1;
return result;
})));
// construct a virtual pattern
const bundlePattern = await factory(
environment.name,
base,
config,
transforms,
filters,
cache
);
envSpinner.text = `${envr}: read ✔`;
// add the built patterns as dependencies
const env = {name: environment.name, version: environment.version};
envSpinner.text = `${envr}: read ✔ | transform`;
bundlePattern.inject(env, readPatterns);
// build the bundle
const builtBundle = await bundlePattern.transform();
envSpinner.text = `${envr}: read ✔ | transform ✔`;
let writeCount = 0;
const artifacts = Object.entries(builtBundle.results);
// write the bundle
const writing = artifacts
.map(throat(1, async entry => {
const [resultName, result] = entry;
const resultPath = path.resolve(
buildBase,
resultName.toLowerCase(),
`${environment.name}.${result.out}`
);
envSpinner.text = `${envr}: read ✔ | transform ✔ | write [files: ${writeCount}/${artifacts.length}]`;
const written = await writeSafe(resultPath, result.buffer);
writeCount += 1;
return written;
}));
await Promise.all(writing);
envSpinner.text = `${envr}: read ✔ | transform ✔ | write ✔`;
envSpinner.succeed();
envSpinner.stop();
envCount += 1;
}
)));
const copySpinner = ora().start();
copySpinner.text = `copying static files`;
await copyStatic(cwd, buildBase);
copySpinner.text = `copied static files`;
copySpinner.succeed();
copySpinner.stop();
const messages = uniq(warnings)
.map(warning => warning.join(' '));
messages.forEach(message => {
console.log(boxen(message, {borderColor: 'yellow', padding: 1}));
});
};