build(aio): refactor and test the example-boilerplate tool

This commit is contained in:
Peter Bacon Darwin 2017-07-27 08:29:17 +01:00 committed by Alex Rickabaugh
parent 51f1da1b85
commit ecff8e6c93
5 changed files with 238 additions and 150 deletions

View File

@ -33,8 +33,9 @@
"docs-test": "node tools/transforms/test.js",
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
"boilerplate:add": "node ./tools/examples/add-example-boilerplate add",
"boilerplate:remove": "node ./tools/examples/add-example-boilerplate remove",
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
"boilerplate:test": "node tools/examples/test.js",
"generate-plunkers": "node ./tools/plunker-builder/generatePlunkers",
"generate-zips": "node ./tools/example-zipper/generateZips",
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",

View File

@ -1,148 +0,0 @@
const fs = fsExtra = require('fs-extra');
const globby = require('globby');
const path = require('path');
const Q = require("q");
const shelljs = require('shelljs');
const EXAMPLES_PATH = path.join(__dirname, '/../../content/examples');
const SHARED_PATH = path.join(__dirname, '/shared');
const BOILERPLATE_PATH = path.join(SHARED_PATH, 'boilerplate');
const EXAMPLES_TESTING_PATH = path.join(EXAMPLES_PATH, 'testing');
const files = {
exampleBoilerplate: [
'src/styles.css',
'src/systemjs-angular-loader.js',
'src/systemjs.config.js',
'src/tsconfig.json',
'bs-config.json',
'bs-config.e2e.json',
'package.json',
'tslint.json'
],
exampleUnitTestingBoilerplate: [
'src/browser-test-shim.js',
'karma-test-shim.js',
'karma.conf.js'
],
exampleConfigFilename: 'example-config.json'
};
// requires admin access because it adds symlinks
function add() {
const realPath = path.join(SHARED_PATH, '/node_modules');
const nodeModulesPaths = getNodeModulesPaths(EXAMPLES_PATH);
// we install the examples modules first
installNodeModules();
nodeModulesPaths.map((linkPath) => {
fs.ensureSymlinkSync(realPath, linkPath);
});
return copyExampleBoilerplate();
}
function copyExampleBoilerplate() {
console.log('Copying example boilerplate files');
const examplePaths = getExamplePaths(EXAMPLES_PATH);
// Make boilerplate files read-only to avoid that they be edited by mistake.
const destFileMode = '444';
let foo = copyFiles(files.exampleBoilerplate, BOILERPLATE_PATH, examplePaths, destFileMode)
// copy the unit test boilerplate
.then(() => {
const unittestPaths = getUnitTestingPaths(EXAMPLES_PATH);
return copyFiles(files.exampleUnitTestingBoilerplate, EXAMPLES_TESTING_PATH, unittestPaths, destFileMode);
})
.catch((err) => {
console.error(err);
throw err;
});
}
// Copies fileNames into destPaths, setting the mode of the
// files at the destination as optional_destFileMode if given.
// returns a promise
function copyFiles(fileNames, originPath, destPaths, optional_destFileMode) {
const copy = Q.denodeify(fsExtra.copy);
const chmod = Q.denodeify(fsExtra.chmod);
let copyPromises = [];
destPaths.map((destPath) => {
fileNames.forEach((fileName) => {
const originName = path.join(originPath, fileName);
const destName = path.join(destPath, fileName);
let p = copy(originName, destName, { clobber: true});
if(optional_destFileMode !== undefined) {
p = p.then(() => {
return chmod(destName, optional_destFileMode);
});
}
copyPromises.push(p);
});
});
return Q.all(copyPromises);
}
function getExamplePaths(basePath, includeBase) {
// includeBase defaults to false
return getPaths(basePath, files.exampleConfigFilename, includeBase);
}
function getFilenames(basePath, filename, includeBase) {
let includePatterns = [path.join(basePath, "**/" + filename)];
if (!includeBase) {
// ignore (skip) the top level version.
includePatterns.push("!" + path.join(basePath, "/" + filename));
}
// ignore (skip) the files in BOILERPLATE_PATH.
includePatterns.push("!" + path.join(BOILERPLATE_PATH, "/" + filename));
const nmPattern = path.join(basePath, "**/node_modules/**");
return globby.sync(includePatterns, {ignore: [nmPattern]});
}
function getNodeModulesPaths(basePath) {
return getExamplePaths(basePath).map((examplePath) => {
return path.join(examplePath, "/node_modules");
});
}
function getPaths(basePath, filename, includeBase) {
const filenames = getFilenames(basePath, filename, includeBase);
return filenames.map((fileName) => {
return path.dirname(fileName);
});
}
function getUnitTestingPaths(basePath) {
const examples = getPaths(basePath, files.exampleConfigFilename, true);
return examples.filter((example) => {
const exampleConfig = fs.readJsonSync(`${example}/${files.exampleConfigFilename}`, {throws: false});
return exampleConfig && !!exampleConfig.unittesting;
});
}
function installNodeModules() {
shelljs.exec('yarn', {cwd: SHARED_PATH});
}
function remove() {
shelljs.exec('git clean -xdf', {cwd: EXAMPLES_PATH});
}
module.exports = {
add: add,
remove: remove
};
// being executed from shell script
switch (process.argv[2]) {
case 'add':
add();
break;
case 'remove':
remove();
break;
default:
console.error(`There is no function with the name: ${process.argv}`);
}

View File

@ -0,0 +1,103 @@
const fs = require('fs-extra');
const glob = require('glob');
const path = require('canonical-path');
const shelljs = require('shelljs');
const yargs = require('yargs');
const SHARED_PATH = path.resolve(__dirname, 'shared');
const SHARED_NODE_MODULES_PATH = path.resolve(SHARED_PATH, 'node_modules');
const BOILERPLATE_BASE_PATH = path.resolve(SHARED_PATH, 'boilerplate');
const EXAMPLES_BASE_PATH = path.resolve(__dirname, '../../content/examples');
const TESTING_BASE_PATH = path.resolve(EXAMPLES_BASE_PATH, 'testing');
const BOILERPLATE_SRC_PATHS = [
'src/styles.css',
'src/systemjs-angular-loader.js',
'src/systemjs.config.js',
'src/tsconfig.json',
'bs-config.json',
'bs-config.e2e.json',
'package.json',
'tslint.json'
];
const BOILERPLATE_TEST_PATHS = [
'src/browser-test-shim.js',
'karma-test-shim.js',
'karma.conf.js'
];
const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
class ExampleBoilerPlate {
/**
* Add boilerplate files to all the examples
*
*/
add() {
// first install the shared node_modules
this.installNodeModules(SHARED_PATH);
// Get all the examples folders, indicated by those that contain a `example-config.json` file
const exampleFolders = this.getFoldersContaining(EXAMPLES_BASE_PATH, EXAMPLE_CONFIG_FILENAME, 'node_modules');
exampleFolders.forEach(exampleFolder => {
// Link the node modules - requires admin access (on Windows) because it adds symlinks
const destinationNodeModules = path.resolve(exampleFolder, 'node_modules');
fs.ensureSymlinkSync(SHARED_NODE_MODULES_PATH, destinationNodeModules);
// Copy the boilerplate source files
BOILERPLATE_SRC_PATHS.forEach(filePath => this.copyFile(BOILERPLATE_BASE_PATH, exampleFolder, filePath));
// Copy the boilerplate test files (if the example is configured to do unit testing)
const exampleConfig = this.loadJsonFile(path.resolve(exampleFolder, EXAMPLE_CONFIG_FILENAME));
if (exampleConfig.unittesting) {
BOILERPLATE_TEST_PATHS.forEach(filePath => this.copyFile(TESTING_BASE_PATH, exampleFolder, filePath));
}
});
}
/**
* Remove all the boilerplate files from all the examples
*/
remove() {
shelljs.exec('git clean -xdfq', {cwd: EXAMPLES_BASE_PATH});
}
main() {
yargs
.usage('$0 <cmd> [args]')
.command('add', 'add the boilerplate to each example', argv => this.add(argv.local))
.command('remove', 'remove the boilerplate from each example', () => this.remove())
.demandCommand(1, 'Please supply a command from the list above')
.argv;
}
installNodeModules(basePath) {
shelljs.exec('yarn', {cwd: basePath});
}
getFoldersContaining(basePath, filename, ignore) {
const pattern = path.resolve(basePath, '**', filename);
const ignorePattern = path.resolve(basePath, '**', ignore, '**');
return glob.sync(pattern, { ignore: [ignorePattern] }).map(file => path.dirname(file));
}
copyFile(sourceFolder, destinationFolder, filePath) {
const sourcePath = path.resolve(sourceFolder, filePath);
const destinationPath = path.resolve(destinationFolder, filePath);
fs.copySync(sourcePath, destinationPath, { overwrite: true });
fs.chmodSync(destinationPath, 444);
}
loadJsonFile(filePath) {
return fs.readJsonSync(filePath, {throws: false}) || {};
}
}
module.exports = new ExampleBoilerPlate();
// If this file was run directly then run the main function,
if (require.main === module) {
module.exports.main();
}

View File

@ -0,0 +1,115 @@
const exampleBoilerPlate = require('./example-boilerplate');
const shelljs = require('shelljs');
const fs = require('fs-extra');
const glob = require('glob');
const path = require('canonical-path');
describe('example-boilerplate tool', () => {
describe('add', () => {
const numberOfBoilerPlateFiles = 8;
const numberOfBoilerPlateTestFiles = 3;
const exampleFolders = ['a/b', 'c/d'];
beforeEach(() => {
spyOn(exampleBoilerPlate, 'installNodeModules');
spyOn(exampleBoilerPlate, 'getFoldersContaining').and.returnValue(exampleFolders);
spyOn(fs, 'ensureSymlinkSync');
spyOn(exampleBoilerPlate, 'copyFile');
spyOn(exampleBoilerPlate, 'loadJsonFile').and.returnValue({});
});
it('should install the node modules', () => {
exampleBoilerPlate.add();
expect(exampleBoilerPlate.installNodeModules).toHaveBeenCalledWith(path.resolve(__dirname, 'shared'));
});
it('should process all the example folders', () => {
exampleBoilerPlate.add();
expect(exampleBoilerPlate.getFoldersContaining).toHaveBeenCalledWith(path.resolve(__dirname, '../../content/examples'), 'example-config.json', 'node_modules');
});
it('should symlink the node_modules', () => {
exampleBoilerPlate.add();
expect(fs.ensureSymlinkSync).toHaveBeenCalledTimes(exampleFolders.length);
expect(fs.ensureSymlinkSync).toHaveBeenCalledWith(path.resolve(__dirname, 'shared/node_modules'), path.resolve('a/b/node_modules'));
expect(fs.ensureSymlinkSync).toHaveBeenCalledWith(path.resolve(__dirname, 'shared/node_modules'), path.resolve('c/d/node_modules'));
});
it('should copy all the source boilerplate files', () => {
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes(numberOfBoilerPlateFiles * exampleFolders.length);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(path.resolve(__dirname, 'shared/boilerplate'), 'a/b', 'package.json');
});
it('should try to load the example config file', () => {
exampleBoilerPlate.add();
expect(exampleBoilerPlate.loadJsonFile).toHaveBeenCalledTimes(exampleFolders.length);
expect(exampleBoilerPlate.loadJsonFile).toHaveBeenCalledWith(path.resolve('a/b/example-config.json'));
expect(exampleBoilerPlate.loadJsonFile).toHaveBeenCalledWith(path.resolve('c/d/example-config.json'));
});
it('should copy all the test boilerplate files if unit testing is configured', () => {
// configure unit testing for example a/b and not c/d
exampleBoilerPlate.loadJsonFile.and.callFake(filePath => filePath.indexOf('a/b') !== -1 ? { unittesting: true } : {});
exampleBoilerPlate.add();
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledTimes((numberOfBoilerPlateFiles * 2) + numberOfBoilerPlateTestFiles);
// for example
expect(exampleBoilerPlate.copyFile).toHaveBeenCalledWith(path.resolve(__dirname, '../../content/examples/testing'), 'a/b', 'karma.conf.js');
expect(exampleBoilerPlate.copyFile).not.toHaveBeenCalledWith(path.resolve(__dirname, '../../content/examples/testing'), 'c/d', 'karma.conf.js');
});
});
describe('remove', () => {
it('should run `git clean`', () => {
spyOn(shelljs, 'exec');
exampleBoilerPlate.remove();
expect(shelljs.exec).toHaveBeenCalledWith('git clean -xdfq', {cwd: path.resolve(__dirname, '../../content/examples') });
});
});
describe('installNodeModules', () => {
it('should run `yarn` in the base path', () => {
spyOn(shelljs, 'exec');
exampleBoilerPlate.installNodeModules('some/base/path');
expect(shelljs.exec).toHaveBeenCalledWith('yarn', { cwd: 'some/base/path' });
});
});
describe('getFoldersContaining', () => {
it('should use glob.sync', () => {
spyOn(glob, 'sync').and.returnValue(['a/b/config.json', 'c/d/config.json']);
const result = exampleBoilerPlate.getFoldersContaining('base/path', 'config.json', 'node_modules');
expect(glob.sync).toHaveBeenCalledWith(path.resolve('base/path/**/config.json'), { ignore: [path.resolve('base/path/**/node_modules/**')] });
expect(result).toEqual(['a/b', 'c/d']);
});
});
describe('copyFile', () => {
it('should use copySync and chmodSync', () => {
spyOn(fs, 'copySync');
spyOn(fs, 'chmodSync');
exampleBoilerPlate.copyFile('source/folder', 'destination/folder', 'some/file/path');
expect(fs.copySync).toHaveBeenCalledWith(
path.resolve('source/folder/some/file/path'),
path.resolve('destination/folder/some/file/path'),
{ overwrite: true });
expect(fs.chmodSync).toHaveBeenCalledWith(path.resolve('destination/folder/some/file/path'), 444);
});
});
describe('loadJsonFile', () => {
it('should use fs.readJsonSync', () => {
spyOn(fs, 'readJsonSync').and.returnValue({ some: 'value' });
const result = exampleBoilerPlate.loadJsonFile('some/file');
expect(fs.readJsonSync).toHaveBeenCalledWith('some/file', {throws: false});
expect(result).toEqual({ some: 'value' });
});
it('should return an empty object if readJsonSync fails', () => {
spyOn(fs, 'readJsonSync').and.returnValue(null);
const result = exampleBoilerPlate.loadJsonFile('some/file');
expect(result).toEqual({});
});
});
});

View File

@ -0,0 +1,17 @@
/*
* Use this script to run the tests for the doc generation
* We cannot use the Jasmine CLI directly because it doesn't seem to
* understand the glob and only runs one spec file.
*
* Equally we cannot use a jasmine.json config file because it doesn't
* allow us to set the projectBaseDir, which means that you have to run
* jasmine CLI from this directory.
*
* Using a file like this gives us full control and keeps the package.json
* file clean and simple.
*/
const Jasmine = require('jasmine');
const jasmine = new Jasmine({ projectBaseDir: __dirname });
jasmine.loadConfig({ spec_files: ['*.spec.js'] });
jasmine.execute();