fusion-scaffolder/index.js (94 lines of code) (raw):
/** Copyright (c) 2018 Uber Technologies, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
const fs = require('fs');
const mkdirpCb = require('mkdirp');
const {render} = require('nunjucks');
const {join, relative, dirname, extname} = require('path');
const {promisify} = require('util');
const readdir = require('recursive-readdir');
const {spawn} = require('child_process');
const copyFile = promisify(fs.copyFile);
const lstat = promisify(fs.lstat);
const mkdirp = promisify(mkdirpCb);
const writeFile = promisify(fs.writeFile);
/*::
type ScaffoldContext = {
cwd?: string,
path?: string,
project?: string,
projectPath?: string,
packageJsonFields?: {
[string]: any
},
};
*/
module.exports = async function scaffold(ctx /*: ScaffoldContext */ = {}) {
if (!ctx.cwd) {
ctx.cwd = process.cwd();
}
const {cwd, path, project, projectPath, packageJsonFields} = ctx;
if (!path) {
throw new Error(
"No template path was passed. There's nothing to scaffold. Please pass a `path` option specifying the directory of the scaffold template."
);
}
if (!project) {
throw new Error(
'No project name was passed. Please pass a `project` option to specify the name of your newly scaffolded project.'
);
}
const computedProjectPath = projectPath || join(cwd, project);
const templatePath = join(cwd, path);
const contentPath = join(templatePath, 'content');
await compile(computedProjectPath, templatePath, contentPath, ctx);
await updatePackageJsonFields(computedProjectPath, packageJsonFields);
await install(computedProjectPath);
};
async function updatePackageJsonFields(projectPath, packageJsonFields = null) {
if (!packageJsonFields) {
return;
}
// Setup package.json files, as we can't have these in the source code because publishing will fail.
const packageJsonPath = `${projectPath}/package.json`;
// $FlowFixMe
let packageContent = require(packageJsonPath); // eslint-disable-line import/no-dynamic-require
packageContent = {...packageContent, ...packageJsonFields};
packageContent.files = ['dist', 'src'];
fs.writeFileSync(packageJsonPath, JSON.stringify(packageContent, null, ' '));
}
async function compile(projectPath, templatePath, contentPath, ctx) {
// Get context from index.js
/* eslint-disable import/no-dynamic-require */
// $FlowFixMe
const getContext = require(join(templatePath, 'index.js'));
/* eslint-enable import/no-dynamic-require */
const newCtx = await getContext(ctx);
// Get all template files
const files = await readdir(contentPath);
return await Promise.all(
files.map(async filePath => {
const relativeFilePath = relative(contentPath, filePath);
const newFilePath = join(projectPath, relativeFilePath);
const newFileDir = dirname(newFilePath);
// Create directories to new file
await mkdirp(newFileDir);
// If is nunjucks template, compile and remove .njk extension
const fileExt = extname(newFilePath);
if (fileExt === '.njk') {
// Need to stat file for copy file mode
const fileStat = await lstat(filePath);
const compiledContents = render(filePath, newCtx);
const withoutNjkFilePath = newFilePath.replace(/\.njk$/, '');
return await writeFile(withoutNjkFilePath, compiledContents, {
mode: fileStat.mode,
});
}
// Otherwise, copy file unchanged
return await copyFile(filePath, newFilePath);
})
);
}
async function install(projectPath) {
return new Promise((resolve, reject) => {
let child;
if (!/^win/.test(process.platform)) {
// linux
child = spawn('yarn', ['install', '--cwd', projectPath], {
stdio: 'inherit',
});
} else {
// windows
child = spawn(
'cmd',
['/s', '/c', 'yarn', 'install', '--cwd', projectPath],
{
stdio: 'inherit',
}
);
}
child.on('close', code => {
if (code !== 0) {
reject(
new Error(
'Error running `yarn install`; however, the template has been successfully scaffolded.'
)
);
}
resolve();
});
});
}