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",
|
||||
"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",
|
||||
|
|
|
@ -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