From 8258b89c739a576b3a01936692d041f8ab87a8a3 Mon Sep 17 00:00:00 2001 From: Alessandro Bellini Date: Thu, 10 Mar 2016 13:03:42 +0100 Subject: [PATCH] antelope first commit --- .gitignore | 15 ++ LICENSE | 20 +++ README.md | 15 ++ antelope.js | 12 ++ bin/antelope | 2 + lib/automation.js | 117 +++++++++++++ lib/automation/collector.js | 85 ++++++++++ lib/automation/compiler.js | 71 ++++++++ lib/automation/provider.dummy-module.js | 17 ++ lib/automation/provider.js | 50 ++++++ lib/automation/themes.js | 38 +++++ lib/automation/webpack/config.yves.js | 46 +++++ lib/automation/webpack/config.zed.js | 120 +++++++++++++ lib/cli.js | 213 ++++++++++++++++++++++++ lib/context.js | 90 ++++++++++ lib/errors.js | 91 ++++++++++ lib/install.js | 29 ++++ lib/licenses.js | 126 ++++++++++++++ lib/provider.js | 18 ++ lib/test.js | 20 +++ lib/test/collector.js | 97 +++++++++++ lib/test/dependency.js | 81 +++++++++ lib/test/manifest.js | 33 ++++ package.json | 45 +++++ 24 files changed, 1451 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 antelope.js create mode 100755 bin/antelope create mode 100644 lib/automation.js create mode 100644 lib/automation/collector.js create mode 100644 lib/automation/compiler.js create mode 100644 lib/automation/provider.dummy-module.js create mode 100644 lib/automation/provider.js create mode 100644 lib/automation/themes.js create mode 100644 lib/automation/webpack/config.yves.js create mode 100644 lib/automation/webpack/config.zed.js create mode 100644 lib/cli.js create mode 100644 lib/context.js create mode 100644 lib/errors.js create mode 100644 lib/install.js create mode 100644 lib/licenses.js create mode 100644 lib/provider.js create mode 100644 lib/test.js create mode 100644 lib/test/collector.js create mode 100644 lib/test/dependency.js create mode 100644 lib/test/manifest.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93889ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# +# general +# +.DS_Store +logs +*.log +pids +*.pid +*.seed +.grunt +node_modules +bower_components +.sass-cache +.git + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8f61fb5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Spryker Systems GmbH + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c25903b --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Antelope +Frontend automation tool (useful for Spryker projects) + +### Requirements +- `node.js` 5.0.0 or above; +- `npm` 3.6.0 or above; +- the tool *should* be installed as global module: you may need admin privileges. + +### Setup +``` +$ npm install -g github:spryker/antelope +``` + +### Documentation +You can read it on [spryker.github.io](http://spryker.github.io). diff --git a/antelope.js b/antelope.js new file mode 100644 index 0000000..89d727d --- /dev/null +++ b/antelope.js @@ -0,0 +1,12 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +require('colors'); // enable colors everywhere - no need to require again +let cli = require('./lib/cli'); + +cli.init(); diff --git a/bin/antelope b/bin/antelope new file mode 100755 index 0000000..5423868 --- /dev/null +++ b/bin/antelope @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../antelope.js'); diff --git a/lib/automation.js b/lib/automation.js new file mode 100644 index 0000000..a2d2098 --- /dev/null +++ b/lib/automation.js @@ -0,0 +1,117 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let context = require('./context'); +let errors = require('./errors'); +let collector = require('./automation/collector'); +let provider = require('./automation/provider'); +let compiler = require('./automation/compiler'); +let themes = require('./automation/themes'); +let cwd = process.cwd(); + +function processAssets(targetConfigName, configurator, targetPattern, theme) { + return new Promise((resolve, reject) => { + return Promise.all([ + collector.entryPoints(targetPattern, theme), + collector.extensions(targetPattern, theme), + collector.manifests() + ]).then((results) => { + let entryPoints = results[0]; + let extensions = results[1]; + let manifests = results[2]; + let defaultConfig; + let config; + + console.info(`\n${targetConfigName.toUpperCase()}`.bold.gray); + + if (!!theme) { + console.info(`${theme.toUpperCase()} theme`.gray); + } + + console.info(`- entry points:`.gray, + `${entryPoints.count.valid} valid`.green, + '|'.gray, + `${entryPoints.count.duplicates} duplicates`.yellow, + '|'.gray, + `${entryPoints.count.total} total`.cyan); + + try { + defaultConfig = configurator.load(entryPoints.valid, manifests.valid); + } catch (err) { + reject(errors.enrich(err, 'loading default configuration')); + } + + if (extensions.count.valid > 0) { + if (extensions.count.valid > 1) { + console.info('- warning: more than one extension for the same application/theme - first will be processed, the others ignored'.yellow); + } + + try { + let extensionName = R.keys(extensions.valid)[0]; + let extension = provider.import(path.join(cwd, extensions.valid[extensionName])); + config = R.merge(defaultConfig, extension); + + if (!config) { + reject(errors.enrich(new Error('undefined/null/invalid configuration extension returned'), 'extending configuration')); + } + + console.info('- custom configuration loaded:'.gray, extensions.valid[extensionName].magenta); + } catch (extensionErr) { + config = R.clone(defaultConfig); + console.error('- custom configuration ignored due to error: %s'.red, extensionErr); + } + + if (context.has('debug')) { + provider.report(); + } + } else { + config = R.clone(defaultConfig); + console.info('- default configuration loaded'.gray); + } + + compiler.build(config).then(resolve, reject); + }, reject); + }); +} + +function automation(targetConfigName, targetPattern, themePattern) { + let configurator = require(`./automation/webpack/config.${targetConfigName}.js`); + + if (targetConfigName === 'zed') { + return processAssets(targetConfigName, configurator, targetPattern, themePattern); + } + + return R.reduce((promise, theme) => { + return promise.then(() => { + return processAssets(targetConfigName, configurator, targetPattern, theme); + }); + }, Promise.resolve(), themes.getFromPattern(themePattern)); +} + +module.exports = { + run: () => { + let targetPatterns = context.patterns.target(); + let themePatterns = context.patterns.theme(); + + if (context.isAll()) { + return automation('yves', targetPatterns.yves, themePatterns.yves).then(() => { + return automation('zed', targetPatterns.zed); + }); + } + + if (context.isYves()) { + return automation('yves', targetPatterns.yves, themePatterns.yves); + } + + if (context.isZed()) { + return automation('zed', targetPatterns.zed); + } + } +}; diff --git a/lib/automation/collector.js b/lib/automation/collector.js new file mode 100644 index 0000000..b4161ce --- /dev/null +++ b/lib/automation/collector.js @@ -0,0 +1,85 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let globby = require('globby'); +let context = require('../context'); +let errors = require('../errors'); +let cwd = process.cwd(); + +function collect(searchSubjects, searchPaths, basePattern) { + return new Promise((resolve, reject) => { + globby(searchPaths, { + cwd: cwd, + follow: context.has('follow'), + nocase: true + }).then((foundPaths) => { + try { + let duplicatesCountersMap = {}; + let results = { + valid: {}, + duplicates: {}, + count: { + valid: 0, + duplicates: 0, + total: foundPaths.length + } + }; + + R.forEach((foundPath) => { + let name = basePattern ? path.basename(foundPath, basePattern) : foundPath; + foundPath = `./${path.relative(cwd, foundPath)}`; + + if (duplicatesCountersMap[name] >= 1) { + results.duplicates[`${name} (${duplicatesCountersMap[name]++})`] = foundPath; + results.count.duplicates++; + } else { + results.valid[name] = foundPath; + results.count.valid++; + duplicatesCountersMap[name] = 1; + } + }, foundPaths); + + return resolve(results); + } catch (err) { + reject(errors.enrich(err, `${searchSubjects} collector`)); + } + }, reject); + }); +} + +module.exports = { + manifests: () => { + return collect('manifests (package.json)', [ + path.join(cwd, `./vendor/spryker/**/assets/package.json`) + ]); + }, + extensions: (targetPattern, themePattern) => { + targetPattern = targetPattern || ''; + themePattern = themePattern || ''; + + return collect('extensions', [ + path.join(cwd, `./assets/**/*${targetPattern}*${themePattern}*${context.patterns.extension()}`) + ], context.patterns.extension()); + }, + entryPoints: (targetPattern, themePattern) => { + targetPattern = targetPattern || ''; + + if (!!themePattern) { + return collect('entry points', [ + path.join(cwd, `./assets/${targetPattern}/${themePattern}/**/*${context.patterns.entry()}`) + ], context.patterns.entry()); + } + + return collect('entry points', [ + path.join(cwd, `./assets/${targetPattern}/**/*${context.patterns.entry()}`), + path.join(cwd, `./vendor/spryker/**/assets/${targetPattern}/**/*${context.patterns.entry()}`) + ], context.patterns.entry()); + } +}; diff --git a/lib/automation/compiler.js b/lib/automation/compiler.js new file mode 100644 index 0000000..32fae88 --- /dev/null +++ b/lib/automation/compiler.js @@ -0,0 +1,71 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let webpack = require('webpack'); +let context = require('../context'); +let errors = require('../errors'); +let cwd = process.cwd(); + +module.exports = { + build: (config) => { + return new Promise((resolve, reject) => { + console.error('- building assets using webpack...'.gray); + + webpack(config, function(err, stats) { + let withWarnings = false; + + try { + if (err) { + throw errors.enrich(err, 'webpack'); + } + + if (!!stats.compilation.errors && stats.compilation.errors.length > 0) { + R.forEach(function(compilationErr) { + console.error(' - %s'.red, compilationErr); + }, stats.compilation.errors); + + throw errors.enrich(new Error('CompilationError'), 'assets compilation'); + } + + if (!!stats.compilation.fileDependencies) { + console.info(' - built:'.gray, `${stats.compilation.fileDependencies.length}`.green); + + if (context.has('debug') && stats.compilation.fileDependencies.length > 0) { + R.forEach(function(file) { + console.log(' -'.gray, `${path.relative(cwd, file)}`.magenta); + }, stats.compilation.fileDependencies); + } + } + + if (!!stats.compilation.missingDependencies) { + console.warn(' - missing:'.gray, `${stats.compilation.missingDependencies.length}`.yellow); + + if (stats.compilation.missingDependencies.length) { + withWarnings = true; + R.forEach(function(file) { + console.warn(` - ${path.relative(cwd, file)}`.yellow); + }, stats.compilation.missingDependencies); + } + } + + console.log('- build completed'.gray, (withWarnings ? 'with some warning'.yellow : 'successfully'.green)); + } catch (err) { + reject(errors.enrich(err, 'compilation report')); + } + + if (config.watch) { + console.info('- watching assets for changes...'.cyan, '[Ctrl+C to quite]'.gray); + } else { + return resolve(); + } + }); + }); + } +}; diff --git a/lib/automation/provider.dummy-module.js b/lib/automation/provider.dummy-module.js new file mode 100644 index 0000000..e9d308e --- /dev/null +++ b/lib/automation/provider.dummy-module.js @@ -0,0 +1,17 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let cwd = process.cwd(); + +module.exports = (id) => { + let context = module; + context.id = id; + context.exports = {}; + context.paths.push(`${cwd}/node_modules`); + return context; +} diff --git a/lib/automation/provider.js b/lib/automation/provider.js new file mode 100644 index 0000000..920f0a3 --- /dev/null +++ b/lib/automation/provider.js @@ -0,0 +1,50 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let fs = require('graceful-fs'); +let R = require('ramda'); +let cwd = process.cwd(); +var report = {}; + +function createContext(id) { + delete require.cache[require.resolve('./provider.dummy-module')]; + return require('./provider.dummy-module')(id); +} + +function extendedRequire(id) { + let requiredModule; + + try { + requiredModule = require(`${cwd}/node_modules/${id}`); + report[id] = 'project'; + return requiredModule; + } catch (err) { + requiredModule = require(id); + report[id] = 'spy'; + return requiredModule; + } +} + +module.exports = { + import: (id) => { + let moduleContext = createContext(id); + let moduleBody = fs.readFileSync(id, 'utf8'); + let moduleFn = new Function('module', 'exports', 'require', moduleBody); + + moduleFn.apply(moduleContext, [moduleContext, moduleContext.exports, extendedRequire]); + return moduleContext.exports; + }, + report: () => { + R.forEach((id) => { + console.log(' -'.gray, id.magenta, 'imported from'.gray, report[id].cyan); + }, R.keys(report)); + + report = {}; + } +}; diff --git a/lib/automation/themes.js b/lib/automation/themes.js new file mode 100644 index 0000000..5c40045 --- /dev/null +++ b/lib/automation/themes.js @@ -0,0 +1,38 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let R = require('ramda'); +let fs = require('graceful-fs'); +let yvesThemesPath = `${process.cwd()}/assets/Yves`; + +function getThemes(){ + return R.filter((name) => { + try { + return fs.statSync(`${yvesThemesPath}/${name}`).isDirectory(); + } catch (err) { + return false; + } + }, fs.readdirSync(yvesThemesPath)); +} + +module.exports = { + all: () => getThemes(), + getFromPattern: (pattern) => { + if (!pattern) { + return []; + } + + if (pattern === '*') { + return getThemes(); + } + + return R.filter((theme) => { + return theme === pattern; + }, getThemes()); + } +} diff --git a/lib/automation/webpack/config.yves.js b/lib/automation/webpack/config.yves.js new file mode 100644 index 0000000..11156d9 --- /dev/null +++ b/lib/automation/webpack/config.yves.js @@ -0,0 +1,46 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +var path = require('path'); +var context = require('../../context'); +var cwd = process.cwd(); + +// webpack +let webpack = require('webpack'); + +process.mainModule.paths.push(path.join(cwd, './node_modules')); + +module.exports = { + load: (entryPoints) => { + let root = [ + cwd, + path.join(cwd, './node_modules') + ]; + + let config = { + context: cwd, + entry: entryPoints, + resolve: { + root: root + }, + resolveLoader: { + root: process.mainModule.paths + }, + output: { + path: path.join(cwd, './public/Yves/assets/unknown-theme') + }, + plugins: [], + progress: true, + failOnError: false, + debug: context.has('debug'), + watch: context.has('watch') + }; + + return config; + } +}; diff --git a/lib/automation/webpack/config.zed.js b/lib/automation/webpack/config.zed.js new file mode 100644 index 0000000..fe27445 --- /dev/null +++ b/lib/automation/webpack/config.zed.js @@ -0,0 +1,120 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let globby = require('globby'); +let context = require('../../context'); +let cwd = process.cwd(); + +// webpack +let webpack = require('webpack'); +let ExtractTextPlugin = require('extract-text-webpack-plugin'); + +// anchor +let anchor = globby.sync([ + '**/spryker-zed-gui-commons.entry.js' +], { + cwd: cwd, + nocase: true +}); + +process.mainModule.paths.push(path.join(cwd, './node_modules')); + +module.exports = { + load: (entryPoints, manifests) => { + let root = [ + path.join(cwd, './node_modules'), + path.join(cwd, anchor[0], '../../../../../') + ].concat(R.map((manifest) => { + return path.join(path.dirname(manifest), './node_modules'); + }, R.keys(manifests))); + + let config = { + context: cwd, + entry: entryPoints, + resolve: { + root: root, + alias: { + ZedGui: 'Gui/assets/Zed/js/modules/commons' + } + }, + resolveLoader: { + root: process.mainModule.paths + }, + output: { + path: path.join(cwd, './public/Zed'), + filename: 'assets/js/[name].js' + }, + module: { + loaders: [{ + test: /\.css\??(\d*\w*=?\.?)+$/i, + loader: ExtractTextPlugin.extract('style', 'css') + }, { + test: /\.scss$/i, + loader: ExtractTextPlugin.extract('style', 'css!resolve-url!sass?sourceMap') + }, { + test: /\.(ttf|woff2?|eot)\??(\d*\w*=?\.?)+$/i, + loader: 'file?name=/assets/fonts/[name].[ext]' + }, { + test: /\.(jpe?g|png|gif|svg)\??(\d*\w*=?\.?)+$/i, + loader: 'file?name=/assets/img/[name].[ext]' + }] + }, + sassLoader: { + includePaths: process.mainModule.paths + }, + plugins: [ + new webpack.optimize.CommonsChunkPlugin('spryker-zed-gui-commons', 'assets/js/spryker-zed-gui-commons.js'), + new webpack.ProvidePlugin({ + $: 'jquery', + jQuery: 'jquery', + + // legacy provider + SprykerAjax: 'Gui/assets/Zed/js/modules/legacy/SprykerAjax', + SprykerAjaxCallbacks: 'Gui/assets/Zed/js/modules/legacy/SprykerAjaxCallbacks', + SprykerAlert: 'Gui/assets/Zed/js/modules/legacy/SprykerAlert' + }), + new ExtractTextPlugin('assets/css/[name].css', { + allChunks: true + }), + new webpack.DefinePlugin({ + PRODUCTION: context.has('production'), + WATCH: context.has('watch'), + 'require.specified': 'require.resolve' + }), + new webpack.NewWatchingPlugin() + ], + watchOptions: { + poll: true + }, + progress: true, + failOnError: false, + devtool: 'sourceMap', + debug: context.has('debug'), + watch: context.has('watch') + }; + + if (context.has('production')) { + config.plugins = config.plugins.concat([ + new webpack.optimize.UglifyJsPlugin({ + comments: false, + sourceMap: context.has('debug'), + compress: { + warnings: context.has('debug') + }, + mangle: { + except: ['$', 'exports', 'require'] + } + }) + ]); + } + + return config; + } +}; diff --git a/lib/cli.js b/lib/cli.js new file mode 100644 index 0000000..a683c76 --- /dev/null +++ b/lib/cli.js @@ -0,0 +1,213 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let commander = require('commander'); +let exit = require('exit'); +let context = require('./context'); +let errors = require('./errors'); +let install = require('./install'); +let test = require('./test'); +let automation = require('./automation'); +// let licenses = require('./licenses'); +let provider = require('./provider'); +let pkg = require('../package.json'); +let cwd = process.cwd(); +let start = new Date().getTime(); + +function printInfo() { + console.info(`\n${pkg.name.toUpperCase()}`.bold, `(${pkg.version})`); + console.info(`${pkg.description}`); +} + +function exitAndLog(code) { + let end = new Date().getTime(); + let time = end - start; + code = code || 0; + + console.log('\nTool executed in'.gray, `${time / 1000}`.cyan, 'seconds | Exit code:'.gray, !code ? '0'.green : `${code}`.red); + return exit(code); +} + +function runInstall(options) { + context.init('install', options); + + printInfo(); + console.info('\nSettings'.bold.grey); + console.info('- project root (cwd):'.grey, cwd.magenta); + + install.run().then(() => { + console.info('\nInstallation completed'.gray, 'succesfully'.green); + return exitAndLog(); + }).catch((err) => { + return exitAndLog(errors.log('install', err)); + }); +} + +function runTest(options) { + context.init('test', options); + + printInfo(); + console.info('\nSettings'.bold.grey); + console.info('- project root (cwd):'.grey, cwd.magenta); + + test.full().then(() => { + console.info('\nTest completed'.gray, 'succesfully'.green); + return exitAndLog(); + }).catch((err) => { + return exitAndLog(errors.log('test', err)); + }); +} + +function runAutomation(target, options) { + let automationPromise; + + context.init(target || 'all', options); + printInfo(); + + if (!context.hasTarget()) { + console.info('\n[!] No valid target application specified (yves, zed)'.yellow); + console.info('\nFor help using this tool, run:'.grey); + console.info(`${pkg.name} --help`.grey); + return exitAndLog(); + } + + if (context.errors.watchAllTargets()) { + return exitAndLog(errors.log('watchAllTargets')); + } + + if (context.errors.watchYvesAllThemes()) { + return exitAndLog(errors.log('watchYvesAllThemes')); + } + + if (context.errors.watchProduction()) { + return exitAndLog(errors.log('watchProduction')); + } + + if (context.errors.zedTheme()) { + return exitAndLog(errors.log('zedTheme')); + } + + console.info('\nSettings'.bold.grey); + console.info('- project root (cwd):'.grey, cwd.magenta); + console.info('- target:'.grey, context.output.target().cyan); + + if (context.isAll() || context.isYves()) { + console.info('- theme:'.grey, context.output.theme().cyan); + } + + console.info('- mode:'.grey, context.output.mode().cyan); + console.info('- watch:'.grey, `${context.has('watch')}`.cyan); + + if (context.has('debug')) { + automationPromise = test.full().then(() => { + return automation.run(); + }); + } else { + automationPromise = test.base().then(() => { + return automation.run(); + }); + } + + automationPromise.then(() => { + return exitAndLog(); + }).catch((err) => { + return exitAndLog(errors.log('automation', err)); + }); +} + +// function runLicensesCrawler(options) { +// context.init('licenses', options); + +// if (!context.isLicensesPrinting()) { +// printInfo(); +// console.info('\nLicenses'.bold.gray); +// console.info('Search for external modules licenses'.gray); +// console.info('-'.gray, 'green'.green, 'is for found and declared licenses'.gray); +// console.info('-'.gray, 'yellow'.yellow, 'is for found but not declared licenses, or viceversa'.gray); +// console.info('-'.gray, 'red'.red, 'is for unknown licenses\n'.gray); +// } + +// licenses.find().then(() => { +// if (context.isLicensesPrinting()) { +// return exit(0); +// } + +// return exitAndLog(); +// }).catch((err) => { +// return exitAndLog(errors.log('licenses', err)); +// }); +// } + +function runProvider(options) { + context.init('provider', options); + + printInfo(); + console.info('\nProvider'.bold.gray); + console.info('Show provided modules available in YVES and ZED custom configurations'.gray, '\n'); + provider.show(); + + return exitAndLog(); +} + +commander + .on('--help', () => { + console.log(` Run 'spy --help' to output command specific usage information`); + }); + +commander + .command('install') + .description('Install all project dependencies') + .action(runInstall); + +commander + .command('test') + .description('Perform functionality and project checks') + .action(runTest); + +commander + .command('build [app]') + .description('Process target application(s) entry points') + .option('-t, --theme ', 'YVES only feature: build provided theme assets') + .option('-w, --watch', 'enable the watch mode over source files') + .option('-f, --follow', 'follow symlinks when search for extensions and entry points') + .option('-d, --debug', 'enable the debug mode (output verbosity)') + .option('--production', 'enable the production mode') + .action(runAutomation); + +// commander +// .command('licenses') +// .description('Crowl through the project and output all npm modules licenses') +// .option('-m, --manifest', 'export the license manifest content (where available)') +// .option('-j, --json', 'output a json formatted list of licenses') +// .option('-c, --csv', 'output a csv formatted table of licenses') +// .option('-s, --separator', 'define the csv separator char (default is ;)') +// .action(runLicensesCrawler) +// .on('--help', () => { +// console.log(` Follow these tips if you want to to write licenses into a file:`, '\n'); +// console.log(` $ spy licenses --json > json_filename.json`); +// console.log(` $ spy licenses --manifest --csv > csv_filename.csv`, '\n'); +// }); + +// commander +// .command('provider') +// .description('Output all the modules SPY provide internally') +// .action(runProvider); + +module.exports = { + init: () => { + if (process.argv.length < 3) { + printInfo(); + commander.outputHelp(); + return exitAndLog(); + } + + commander + .version(pkg.version) + .parse(process.argv); + } +} diff --git a/lib/context.js b/lib/context.js new file mode 100644 index 0000000..28e569f --- /dev/null +++ b/lib/context.js @@ -0,0 +1,90 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let cwd = process.cwd(); +let command = ''; +let options = {}; +let args = []; + +const ENTRY_PATTERN = '.entry.js'; +const EXTENSION_PATTERN = '.spy.js'; +const YVES_DEFAULT_PATTERN = 'Yves'; +const ZED_DEFAULT_PATTERN = 'Zed'; + +function is(cmd) { + return command === cmd; +} + +module.exports = { + init: (cmd, opts) => { + command = cmd || ''; + options = R.clone(opts || {}); + }, + isAll: () => is('all'), + isYves: () => is('yves'), + isZed: () => is('zed'), + isLicenses: () => is('licenses'), + isLicensesPrinting: () => is('licenses') && (options.json || options.csv), + isTest: () => is('test'), + options: () => R.clone(options), + set: (name, value) => options[name] = value, + get: (name) => options[name] || null, + has: (name) => !!options[name], + hasTarget: () => is('all') || is('yves') || is('zed'), + output: { + mode: () => (!!options.production ? 'production' : 'development') + (!!options.debug ? ' (debug)' : ''), + target: () => { + let output = command || 'none'; + + switch (command) { + case 'all': + return 'YVES and ZED'; + default: + return output.toUpperCase(); + } + }, + theme: () => { + switch (command) { + case 'all': + return options.theme || 'all'; + case 'yves': + return options.theme || 'all'; + case 'zed': + return 'none'; + default: + return ''; + } + } + }, + patterns: { + entry: () => ENTRY_PATTERN, + extension: () => EXTENSION_PATTERN, + defaultYves: () => YVES_DEFAULT_PATTERN, + defaultZed: () => ZED_DEFAULT_PATTERN, + target: () => { + return { + yves: is('yves') || is('all') ? YVES_DEFAULT_PATTERN : null, + zed: is('zed') || is('all') ? ZED_DEFAULT_PATTERN : null + }; + }, + theme: () => { + return { + yves: is('yves') || is('all') ? (options.theme || '*') : '*', + zed: null + }; + } + }, + errors: { + watchAllTargets: () => is('all') && !!options.watch, + watchYvesAllThemes: () => is('yves') && !!options.watch && !options.theme, + watchProduction: () => !!options.watch && !!options.production, + zedTheme: () => is('zed') && !!options.theme + } +}; diff --git a/lib/errors.js b/lib/errors.js new file mode 100644 index 0000000..091a65f --- /dev/null +++ b/lib/errors.js @@ -0,0 +1,91 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let R = require('ramda'); + +let errors = { + licenses: { + name: 'License crawler error', + description: 'Something went wrong during licenses crawl' + }, + test: { + name: 'Test error' + }, + automation: { + name: 'Automation error', + description: 'Something went wrong during automation process' + }, + watchAllTargets: { + name: 'Watch conflict detected', + description: 'Cannot enable watch mode for YVES and ZED at the same time' + }, + watchYvesAllThemes: { + name: 'Watch conflict detected', + description: `Cannot enable watch mode for all YVES themes at the same time. Use '-t' or '--theme' to watch over a specific one` + }, + watchProduction: { + name: 'Watch conflict detected', + description: 'Cannot enable watch mode in production mode' + }, + zedTheme: { + name: 'Argument not allowed', + description: `'zed' command has no 'theme' argument: ZED application has no themes` + } +}; + +let ids = R.keys(errors); + +class SpyError { + constructor(id, exception) { + this._id = id; + this._exception = exception || null; + this._error = errors[id] || {}; + } + get id() { + return this._id; + } + get name() { + return this._error.name || `${this._id} [unknown]`; + } + get description() { + return !!this._error.description ? this._error.description : ''; + } + get code() { + return R.indexOf(this._id, ids) + 2; + } + get exception() { + return !!this._exception ? this._exception : null; + } +} + +module.exports = { + log: (id, err) => { + let error = new SpyError(id, err); + + console.error(`\n[!] ${error.name}: execution aborted (err: ${error.id})`.red); + + if (error.description) { + console.error(` ${error.description}`.red); + } + + if (error.exception) { + console.error(` ${error.exception}`.red); + + if (error.exception.info) { + console.error(` Trace info: ${error.exception.info}`.red); + } + } + + return error.code; + }, + enrich: (err, info) => { + err.info = err.info || []; + err.info.push(info); + return err; + } +}; diff --git a/lib/install.js b/lib/install.js new file mode 100644 index 0000000..4dbc54c --- /dev/null +++ b/lib/install.js @@ -0,0 +1,29 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let shell = require('shelljs'); +let collector = require('./automation/collector'); +let cwd = process.cwd(); + +module.exports = { + run: () => { + return new Promise((resolve, reject) => { + collector.manifests().then((list) => { + R.forEach((manifest) => { + shell.cd(path.dirname(manifest)); + shell.exec(`npm install`); + }, R.keys(list.valid)); + + shell.cd(cwd); + resolve(); + }, (err) => reject(err)); + }); + } +}; diff --git a/lib/licenses.js b/lib/licenses.js new file mode 100644 index 0000000..cdc83fa --- /dev/null +++ b/lib/licenses.js @@ -0,0 +1,126 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let fs = require('graceful-fs'); +let R = require('ramda'); +let globby = require('globby'); +let context = require('./context'); +let errors = require('./errors'); +let cwd = process.cwd(); + +function toCSV(json) { + let sep = context.get('separator'); + let headers = `Name${sep}Url${sep}License Terms${sep}License Type${sep}Version${sep}Type${sep}Labeling${sep}\n`; + + return R.reduce((output, key) => { + let pkg = json[key]; + output += `"${key}"${sep}"${pkg.url}"${sep}${sep}"${pkg.license.type}"${sep}"${pkg.version}"${sep}"${pkg.type}"${sep}"${pkg.license.terms}"${sep}\n`; + return output; + }, headers, R.keys(json)); +} + +module.exports = { + find: () => { + return new Promise((resolve, reject) => { + globby([ + '**/package.json', + '!package.json' + ], { + cwd: cwd, + nocase: true + }).then((pkgs) => { + let json = R.reduce((licenses, pkgRelPath) => { + try { + let pkgPath = path.join(cwd, pkgRelPath); + let pkgDir = path.dirname(pkgPath); + let pkg = require(pkgPath); + + let license = { + type: '', + path: '', + terms: '', + found: false, + declared: false + }; + + let termsFiles = globby.sync([ + '**license*', + '**licence*' + ], { + cwd: pkgDir, + nocase: true + }); + + R.forEach((termsFile) => { + let termsAbsPath = path.join(pkgDir, termsFile); + license.path += `${path.relative(cwd, termsAbsPath)}, `; + + if (context.has('manifest')) { + license.terms += `${fs.readFileSync(termsAbsPath, 'utf8')}\n\n`; + } + }, termsFiles); + + if (pkg.licenses && pkg.licenses.length > 0) { + license.type = R.reduce((type, details) => type + `${details.type} `, '', pkg.licenses).trim(); + license.declared = !!license.type; + } else { + license.declared = !!pkg.license; + license.type = pkg.license || 'unknown'; + } + + license.found = !!license.path; + license.path = license.found ? license.path.trim().replace(/,$/, '') : 'not found'; + + licenses[pkg.name] = { + type: 'npm', + version: pkg.version || 'unknown', + url: pkg.homepage, + license: license, + }; + + return licenses; + } catch (err) { + throw errors.enrich(err, 'license crawler'); + } + }, {}, pkgs); + + if (context.has('json')) { + console.info(JSON.stringify(json)); + return resolve(json); + } + + if (context.has('csv')) { + console.info(toCSV(json)); + return resolve(json); + } + + R.forEach((key) => { + let pkg = json[key]; + let licenceType = pkg.license.type.green; + + if (!pkg.license.declared && pkg.license.found) { + licenceType = pkg.license.path.yellow; + } + + if (pkg.license.declared && !pkg.license.found) { + licenceType = pkg.license.type.yellow; + } + + if (!pkg.license.declared && !pkg.license.found) { + licenceType = pkg.license.type.red; + } + + console.info('-'.gray, key.magenta, pkg.version.cyan, '->'.gray, licenceType); + }, R.keys(json)); + + return resolve(json); + }); + }); + } +}; diff --git a/lib/provider.js b/lib/provider.js new file mode 100644 index 0000000..98ef079 --- /dev/null +++ b/lib/provider.js @@ -0,0 +1,18 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let R = require('ramda'); +let pkg = require('../package.json'); + +module.exports = { + show: () => { + R.forEach((key) => { + console.log('-'.gray, key.magenta, pkg.dependencies[key].cyan); + }, R.keys(pkg.dependencies)); + } +} diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..c2d9e91 --- /dev/null +++ b/lib/test.js @@ -0,0 +1,20 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +// let manifestTest = require('./test/manifest'); +// let dependencyTest = require('./test/dependency'); +let collectorTest = require('./test/collector'); + +module.exports = { + base: () => { + return Promise.resolve(); + }, + full: () => { + return collectorTest.run(); + } +}; diff --git a/lib/test/collector.js b/lib/test/collector.js new file mode 100644 index 0000000..50bf6bc --- /dev/null +++ b/lib/test/collector.js @@ -0,0 +1,97 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let context = require('../context'); +let errors = require('../errors'); +let collector = require('../automation/collector'); + +function yvesEntities(entities) { + return R.pickBy((path, name) => { + return path.search(new RegExp(context.patterns.defaultYves(), 'i')) > -1; + }, entities); +} + +function zedEntities(entities) { + return R.pickBy((path, name) => { + return path.search(new RegExp(context.patterns.defaultZed(), 'i')) > -1; + }, entities); +} + +function unknownEntities(entities) { + return R.pickBy((path, name) => { + return path.search(new RegExp(context.patterns.defaultYves(), 'i')) > -1 + && path.search(new RegExp(context.patterns.defaultZed(), 'i')) > -1; + }, entities); +} + +function log(target, collectedResults, warnMessage) { + let collectedResultsNames = R.keys(collectedResults); + + if (!!warnMessage) { + console.log(`| - ${target}:`.grey, `${collectedResultsNames.length}`.yellow); + } else { + console.log(`| - ${target}:`.grey, `${collectedResultsNames.length}`.cyan); + } + + if (collectedResultsNames.length > 0) { + R.forEach((name) => { + if (!!warnMessage) { + console.log(`| ${String.fromCharCode(0x2517)} ${name}:`.gray, collectedResults[name].yellow); + } else { + console.log(`| ${String.fromCharCode(0x2517)} ${name}:`.gray, collectedResults[name].magenta); + } + }, collectedResultsNames); + + if (!!warnMessage) { + console.log('|'.grey); + console.warn('|'.grey, warnMessage.yellow); + } + } +} + +module.exports = { + run: () => { + return new Promise((resolve, reject) => { + Promise.all([ + collector.entryPoints(), + collector.extensions() + ]).then((results) => { + try { + let entryPoints = results[0]; + let extensions = results[1]; + + console.log('\nCollector test'.bold.grey); + console.log('Search and analyze all the entities (entry points and extensions) in the project'.gray); + console.log('|'.gray); + console.log('| Entry points:'.grey, `${entryPoints.count.total}`.cyan); + console.log('| - valid:'.grey, `${entryPoints.count.valid}`.cyan); + + log('YVES', yvesEntities(entryPoints.valid)); + log('ZED', zedEntities(entryPoints.valid)); + log('duplicates', entryPoints.duplicates, '[!] Fix duplicates entry points or they may never get builded'); + log('unknown', unknownEntities(entryPoints.valid), '[!] Fix unknown entry points or they may never get builded'); + + console.log('|'.gray); + console.log('| Extensions:'.grey, `${extensions.count.total}`.cyan); + console.log('| - valid:'.grey, `${extensions.count.valid}`.cyan); + + log('YVES', yvesEntities(extensions.valid)); + log('ZED', zedEntities(extensions.valid)); + log('duplicates', extensions.duplicates, '[!] Fix duplicates extensions or they may overwrite the previous one'); + log('unknown', unknownEntities(extensions.valid), '[!] Fix unknown extensions or they may be ignored'); + + return resolve(); + } catch (err) { + throw errors.enrich(err, 'collector test'); + } + }); + }); + } +}; diff --git a/lib/test/dependency.js b/lib/test/dependency.js new file mode 100644 index 0000000..6069b22 --- /dev/null +++ b/lib/test/dependency.js @@ -0,0 +1,81 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let R = require('ramda'); +let context = require('../context'); +let errors = require('../errors'); +let cwd = process.cwd(); +let spyPkg = require('../../package.json'); + +function performAnalysis(targetDependencies, projectDependencies) { + return R.map((dependencyName) => { + return { + name: dependencyName, + isMissing: !projectDependencies[dependencyName], + isDiverged: projectDependencies[dependencyName] != targetDependencies[dependencyName], + current: projectDependencies[dependencyName], + recommended: targetDependencies[dependencyName], + }; + }, R.keys(targetDependencies)); +} + +function log(target, dependencies) { + let missingDependencies = false; + let divergedDependencies = false; + + console.log('|'.gray); + console.log(`| ${target}:`.gray, `${dependencies.length}`.cyan); + + R.forEach((dependency) => { + if (dependency.isMissing) { + missingDependencies = true; + console.warn(`| - ${dependency.name}:`.gray, `missing (${dependency.recommended} needed)`.red); + } else if (dependency.isDiverged) { + divergedDependencies = true; + console.log(`| - ${dependency.name}:`.gray, `${dependency.current}`.yellow, `(${dependency.recommended} recommended)`.gray); + } else { + console.log(`| - ${dependency.name}:`.gray, `${dependency.current}`.green); + } + }, dependencies); + + if (missingDependencies || divergedDependencies) { + console.log('|'.gray); + + if (missingDependencies) { + console.error('|'.gray, `[!] Fix missing dependencies or ${target} may not work properly`.red); + } + + if (divergedDependencies) { + console.warn('|'.gray, `[!] Check diverged dependencies compatibility to avoid ${target} misbehaviours`.yellow); + } + } +} + +module.exports = { + run: () => { + return new Promise((resolve, reject) => { + try { + console.log('\nDependency test'.bold.gray); + console.log('Requirements analysis on mandatory dependencies'.gray); + console.log('Custom dependencies will be ignored'.gray); + + let projectPkg = require(path.join(cwd, './package.json')); + let yvesDependencies = performAnalysis(spyPkg.yvesDependencies, projectPkg.dependencies); + let zedDependencies = performAnalysis(spyPkg.zedDependencies, projectPkg.dependencies); + + log('YVES', yvesDependencies); + log('ZED', zedDependencies); + + return resolve(); + } catch (err) { + throw errors.enrich(err, 'dependency test'); + } + }); + } +}; diff --git a/lib/test/manifest.js b/lib/test/manifest.js new file mode 100644 index 0000000..6412e1d --- /dev/null +++ b/lib/test/manifest.js @@ -0,0 +1,33 @@ +/** + * This file is part of SPY frontend automation tool + * (c) Spryker Systems GmbH + * For full copyright and license information, please view the LICENSE file that was distributed with this source code. + */ + +'use strict'; + +let path = require('path'); +let errors = require('../errors'); +let cwd = process.cwd(); + +module.exports = { + run: () => { + return new Promise((resolve, reject) => { + console.log('\nManifest test'.bold.gray); + console.log('Check for package.json in cwd'.gray); + console.log('|'.gray); + + try { + let projectPkg = require(path.join(cwd, './package.json')); + console.log('| Manifest found:'.gray, `${projectPkg.name || 'no name'} (${projectPkg.version || 'no version'})`.cyan); + + return resolve(); + } catch (err) { + console.error('|'.gray, `[!] No manifest found`.red); + console.error('|'.gray, ' Run'.red, 'npm init', 'to create a new one'.red); + + throw errors.enrich(err, 'manifest test'); + } + }); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..c932580 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "antelope", + "version": "0.1.0", + "description": "Spryker frontend automation tool", + "main": "antelope.js", + "preferGlobal": "true", + "scripts": { + "test": "node antelope test" + }, + "author": { + "name": "Spryker Systems GmbH", + "email": "info@spryker.com", + "url": "http://spryker.com" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/spryker/antelope.git" + }, + "directories": { + "bin": "./bin" + }, + "engines": { + "node": ">=5.0.0", + "npm": ">=3.3.6" + }, + "dependencies": { + "colors": "^1.1.2", + "commander": "^2.9.0", + "css-loader": "^0.23.0", + "exit": "^0.1.2", + "extract-text-webpack-plugin": "^1.0.1", + "file-loader": "^0.8.5", + "globby": "^4.0.0", + "graceful-fs": "^4.1.2", + "node-sass": "^3.4.2", + "ramda": "^0.18.0", + "resolve-url-loader": "^1.4.3", + "sass-loader": "^3.1.1", + "shelljs": "^0.5.3", + "style-loader": "^0.13.0", + "url-loader": "^0.5.7", + "webpack": "^1.12.6" + } +}