build(aio): refactor and test the example-boilerplate tool
This commit is contained in:
parent
51f1da1b85
commit
ecff8e6c93
|
@ -33,8 +33,9 @@
|
||||||
"docs-test": "node tools/transforms/test.js",
|
"docs-test": "node tools/transforms/test.js",
|
||||||
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
||||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
||||||
"boilerplate:add": "node ./tools/examples/add-example-boilerplate add",
|
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||||
"boilerplate:remove": "node ./tools/examples/add-example-boilerplate remove",
|
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
|
||||||
|
"boilerplate:test": "node tools/examples/test.js",
|
||||||
"generate-plunkers": "node ./tools/plunker-builder/generatePlunkers",
|
"generate-plunkers": "node ./tools/plunker-builder/generatePlunkers",
|
||||||
"generate-zips": "node ./tools/example-zipper/generateZips",
|
"generate-zips": "node ./tools/example-zipper/generateZips",
|
||||||
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
||||||
|
|
|
@ -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}`);
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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();
|
Loading…
Reference in New Issue