From 2eb495393dccd025974971c5dbe011bab3368032 Mon Sep 17 00:00:00 2001 From: Even Stensberg Date: Sun, 22 Oct 2017 23:45:54 +0200 Subject: [PATCH] Build Step for the CLI (#190) * chore: Use prettier (#173) * build: Add webpack lint settings and style code with eslint --fix We now use the webpack eslint settings that make sense in the cli. Also use the new --fix of eslint to automatically beautify the code * Add additional .eslintrc to /bin to remove unrelated eslint errors * build: Add jest-cli as dev dependency * style: fix lintin errors and coding style according to webpack settings * build: update dependencies and add package-lock to repo * feat: Require quotes only in property names that require it * chore: Add again removed settings by merge conflict solving * tests: Fix failing test * chore: Use husky instead of pre-commit pre-commit package stopped working. See https://github.com/observing/pre-commit/issues/113 husky also requires less boilerplate in the package.json * [Feature] Added support to Flow inside transformations (#174) * [FLOW] Implemented interfaces and flow types inside the transformations * [FLOW] first commit * Added flwo to two other files * Reviewed interfaces * Created different intefaces for expressions * Fixed rest of trasformations * More developments * Updated code after code review * Fixed failing tests * Removed yarn file * Applied linting * Updated packages * Applied lint style to the imports due to code review * docs: Documentation for migrate (#175) * initial draft of migrate documentation * formatting * formatting * added summary of migrate changes * maintain consistent usage of user through documentation * Feature/generate loader (#183) * Add template files for loader yeoman generator * Create yeoman generator for a webpack loader project * Add tests for loader-generator * Add `mkdirp` dependency for loader-generator * Add function to create yeoman env and run loader-generator * Add `generate-loader` command to webpack-cli * Copy loader templates from proper directory * Add template files for plugin generator * Create yeoman generator for webpack plugins * Add function to create yeoman env and run plugin generator * Add cli command to generate plugin * Register generate- commands in yargs * Add template files for loader examples and tests * Copy loader test and example template files in generator * Add template files for plugin examples and tests * Copy plugin test and example template files in generator * Refactor generator file copying, switch .includes with .indexOf in CLI arg parsing * Change `indexOf('foo') > -1` to `indexOf('foo') >= 0` * Factor out generator copy utilities into separate module * Rewrite generators using a function that returns a customized generator class * Fix linting errors * Remove //eslint-disable lines from template files * Add unit tests for CLI and add flow compilation (#186) * add unit tests for everything and flow compilation * add travis builds * add cli to signature & add ignore to trash code * port to jest * remove redundant cli command * Feature/generate loader (#183) * Add template files for loader yeoman generator * Create yeoman generator for a webpack loader project * Add tests for loader-generator * Add `mkdirp` dependency for loader-generator * Add function to create yeoman env and run loader-generator * Add `generate-loader` command to webpack-cli * Copy loader templates from proper directory * Add template files for plugin generator * Create yeoman generator for webpack plugins * Add function to create yeoman env and run plugin generator * Add cli command to generate plugin * Register generate- commands in yargs * Add template files for loader examples and tests * Copy loader test and example template files in generator * Add template files for plugin examples and tests * Copy plugin test and example template files in generator * Refactor generator file copying, switch .includes with .indexOf in CLI arg parsing * Change `indexOf('foo') > -1` to `indexOf('foo') >= 0` * Factor out generator copy utilities into separate module * Rewrite generators using a function that returns a customized generator class * Fix linting errors * Remove //eslint-disable lines from template files * add unit tests for everything and flow compilation * add travis builds * add cli to signature & add ignore to trash code * port to jest * remove redundant cli command * rebase against master * ast for devServer (#185) * feat: replace yarn backup w/ normal yarn (#160) * feat: replace yarn backup w/ normal yarn * upgrade npm & remove yarn * Use yarn if available (#189) * bump version to 1.3.4 * update package.json * 1.3.5 * prepublish -> postinstall * 1.3.6 * temp fix to prepublish issue * 1.3.7 * add propper post install * 1.3.8 * re-add manual build step * 1.3.9 * refactor * bump version * disable published pkg * add ignore patterns * add more ignore patterns for npm --- .babelrc | 2 +- .eslintignore | 1 + .eslintrc.js | 4 +- .flowconfig | 11 + .gitignore | 3 + .npmignore | 15 + .travis.yml | 1 + bin/webpack.js | 32 +- dist/creator/index.js | 66 ++ dist/creator/index.test.js | 10 + .../__testfixtures__/context-0.input.js | 6 + .../__testfixtures__/context-1.input.js | 6 + .../__testfixtures__/context-2.input.js | 6 + .../transformations/context/context.js | 32 + .../transformations/context/context.test.js | 7 + .../__testfixtures__/devServer-0.input.js | 6 + .../__testfixtures__/devServer-1.input.js | 6 + .../transformations/devServer/devServer.js | 40 ++ .../devServer/devServer.test.js | 10 + .../__testfixtures__/devtool-0.input.js | 6 + .../__testfixtures__/devtool-1.input.js | 6 + .../transformations/devtool/devtool.js | 32 + .../transformations/devtool/devtool.test.js | 8 + .../entry/__testfixtures__/entry-0.input.js | 1 + dist/creator/transformations/entry/entry.js | 41 ++ .../transformations/entry/entry.test.js | 24 + .../__testfixtures__/externals-0.input.js | 6 + .../__testfixtures__/externals-1.input.js | 6 + .../transformations/externals/externals.js | 54 ++ .../externals/externals.test.js | 61 ++ dist/creator/transformations/index.js | 118 ++++ .../module/__testfixtures__/module-0.input.js | 6 + .../module/__testfixtures__/module-1.input.js | 6 + dist/creator/transformations/module/module.js | 32 + .../transformations/module/module.test.js | 108 +++ .../node/__testfixtures__/node-0.input.js | 6 + dist/creator/transformations/node/node.js | 28 + .../creator/transformations/node/node.test.js | 13 + .../other/__testfixtures__/other-0.input.js | 6 + dist/creator/transformations/other/amd.js | 28 + dist/creator/transformations/other/bail.js | 32 + dist/creator/transformations/other/cache.js | 32 + dist/creator/transformations/other/merge.js | 46 ++ .../transformations/other/other.test.js | 13 + dist/creator/transformations/other/profile.js | 32 + .../output/__testfixtures__/output-0.input.js | 3 + dist/creator/transformations/output/output.js | 27 + .../transformations/output/output.test.js | 15 + .../__testfixtures__/performance-0.input.js | 6 + .../performance/performance.js | 28 + .../performance/performance.test.js | 11 + .../__testfixtures__/plugins-0.input.js | 6 + .../transformations/plugins/plugins.js | 33 + .../transformations/plugins/plugins.test.js | 14 + .../__testfixtures__/resolve-0.input.js | 6 + .../transformations/resolve/resolve.js | 28 + .../transformations/resolve/resolve.test.js | 29 + .../stats/__testfixtures__/stats-0.input.js | 6 + dist/creator/transformations/stats/stats.js | 40 ++ .../transformations/stats/stats.test.js | 36 + .../target/__testfixtures__/target-0.input.js | 6 + .../target/__testfixtures__/target-1.input.js | 6 + dist/creator/transformations/target/target.js | 32 + .../transformations/target/target.test.js | 6 + .../__testfixtures__/top-scope-0.input.js | 1 + .../transformations/top-scope/top-scope.js | 21 + .../top-scope/top-scope.test.js | 5 + .../watch/__testfixtures__/watch-0.input.js | 6 + .../watch/__testfixtures__/watch-1.input.js | 6 + .../watch/__testfixtures__/watch-2.input.js | 6 + dist/creator/transformations/watch/watch.js | 32 + .../transformations/watch/watch.test.js | 8 + .../transformations/watch/watchOptions.js | 28 + .../watch/watchOptions.test.js | 21 + dist/creator/utils/run-prettier.js | 38 ++ dist/creator/utils/validate-options.js | 39 ++ dist/creator/utils/validate-options.spec.js | 25 + dist/creator/yeoman/utils/entry.js | 81 +++ dist/creator/yeoman/utils/module.js | 10 + dist/creator/yeoman/utils/plugins.js | 3 + dist/creator/yeoman/utils/tooltip.js | 47 ++ dist/creator/yeoman/utils/validate.js | 9 + dist/creator/yeoman/webpack-adapter.js | 18 + dist/creator/yeoman/webpack-generator.js | 401 +++++++++++ dist/generate-loader/index.js | 17 + dist/generate-loader/loader-generator.js | 58 ++ dist/generate-loader/loader-generator.test.js | 15 + dist/generate-plugin/index.js | 17 + dist/generate-plugin/plugin-generator.js | 39 ++ {lib => dist}/initialize.js | 0 dist/migrate.js | 114 ++++ .../__testfixtures__/failing.js | 80 +++ .../__testfixtures__/bannerPlugin-0.input.js | 5 + .../__testfixtures__/bannerPlugin-1.input.js | 4 + .../__testfixtures__/bannerPlugin-2.input.js | 6 + .../bannerPlugin/bannerPlugin.js | 27 + .../bannerPlugin/bannerPlugin.test.js | 7 + dist/transformations/defineTest.js | 95 +++ .../extractTextPlugin.input.js | 16 + .../extractTextPlugin/extractTextPlugin.js | 58 ++ .../extractTextPlugin.test.js | 5 + dist/transformations/index.js | 89 +++ dist/transformations/index.test.js | 66 ++ .../loaderOptionsPlugin-0.input.js | 6 + .../loaderOptionsPlugin-1.input.js | 9 + .../loaderOptionsPlugin-2.input.js | 9 + .../loaderOptionsPlugin-3.input.js | 17 + .../loaderOptionsPlugin.js | 41 ++ .../loaderOptionsPlugin.test.js | 8 + .../__testfixtures__/loaders-0.input.js | 65 ++ .../__testfixtures__/loaders-1.input.js | 8 + .../__testfixtures__/loaders-2.input.js | 15 + .../__testfixtures__/loaders-3.input.js | 8 + .../__testfixtures__/loaders-4.input.js | 8 + .../__testfixtures__/loaders-5.input.js | 12 + .../__testfixtures__/loaders-6.input.js | 12 + .../__testfixtures__/loaders-7.input.js | 14 + .../__testfixtures__/loaders-8.input.js | 14 + dist/transformations/loaders/loaders.js | 314 +++++++++ dist/transformations/loaders/loaders.test.js | 13 + .../__testfixtures__/outputPath-0.input.js | 5 + .../__testfixtures__/outputPath-1.input.js | 6 + .../__testfixtures__/outputPath-2.input.js | 6 + dist/transformations/outputPath/outputPath.js | 73 ++ .../outputPath/outputPath.test.js | 7 + .../removeDeprecatedPlugins-0.input.js | 6 + .../removeDeprecatedPlugins-1.input.js | 6 + .../removeDeprecatedPlugins-2.input.js | 8 + .../removeDeprecatedPlugins-3.input.js | 7 + .../removeDeprecatedPlugins-4.input.js | 8 + .../removeDeprecatedPlugins.js | 59 ++ .../removeDeprecatedPlugins.test.js | 9 + .../removeJsonLoader-0.input.js | 19 + .../removeJsonLoader-1.input.js | 12 + .../removeJsonLoader-2.input.js | 10 + .../removeJsonLoader-3.input.js | 11 + .../removeJsonLoader/removeJsonLoader.js | 60 ++ .../removeJsonLoader/removeJsonLoader.test.js | 8 + .../resolve/__testfixtures__/resolve.input.js | 20 + dist/transformations/resolve/resolve.js | 68 ++ dist/transformations/resolve/resolve.test.js | 5 + .../uglifyJsPlugin-0.input.js | 5 + .../uglifyJsPlugin-1.input.js | 6 + .../uglifyJsPlugin-2.input.js | 8 + .../uglifyJsPlugin/uglifyJsPlugin.js | 28 + .../uglifyJsPlugin/uglifyJsPlugin.test.js | 7 + dist/transformations/utils.js | 630 ++++++++++++++++++ dist/transformations/utils.test.js | 358 ++++++++++ dist/types.js | 192 ++++++ dist/utils/WebpackOptionsValidationError.js | 249 +++++++ dist/utils/copy-utils.js | 57 ++ dist/utils/npm-exists.js | 34 + dist/utils/npm-exists.spec.js | 16 + dist/utils/npm-packages-exists.js | 38 ++ dist/utils/package-manager.js | 61 ++ dist/utils/package-manager.spec.js | 90 +++ dist/utils/resolve-packages.js | 70 ++ dist/utils/resolve-packages.spec.js | 34 + dist/utils/validateSchema.js | 67 ++ dist/utils/webpack-generator.js | 77 +++ lib/index.js | 44 ++ lib/init.js | 22 + .../__testfixtures__/failing.js | 137 ++-- .../bannerPlugin/bannerPlugin.js | 10 +- .../extractTextPlugin/extractTextPlugin.js | 40 +- lib/transformations/index.js | 18 +- .../loaderOptionsPlugin.js | 14 +- lib/transformations/loaders/loaders.js | 253 +++---- lib/transformations/outputPath/outputPath.js | 30 +- .../removeDeprecatedPlugins.js | 35 +- .../removeJsonLoader/removeJsonLoader.js | 37 +- lib/transformations/resolve/resolve.js | 24 +- .../uglifyJsPlugin/uglifyJsPlugin.js | 6 +- lib/transformations/utils.js | 156 +++-- lib/types.js | 192 ++++++ package-lock.json | 419 ++++++++---- package.json | 15 +- 177 files changed, 6890 insertions(+), 451 deletions(-) create mode 100644 .flowconfig create mode 100644 .npmignore create mode 100644 dist/creator/index.js create mode 100644 dist/creator/index.test.js create mode 100644 dist/creator/transformations/context/__testfixtures__/context-0.input.js create mode 100644 dist/creator/transformations/context/__testfixtures__/context-1.input.js create mode 100644 dist/creator/transformations/context/__testfixtures__/context-2.input.js create mode 100644 dist/creator/transformations/context/context.js create mode 100644 dist/creator/transformations/context/context.test.js create mode 100644 dist/creator/transformations/devServer/__testfixtures__/devServer-0.input.js create mode 100644 dist/creator/transformations/devServer/__testfixtures__/devServer-1.input.js create mode 100644 dist/creator/transformations/devServer/devServer.js create mode 100644 dist/creator/transformations/devServer/devServer.test.js create mode 100644 dist/creator/transformations/devtool/__testfixtures__/devtool-0.input.js create mode 100644 dist/creator/transformations/devtool/__testfixtures__/devtool-1.input.js create mode 100644 dist/creator/transformations/devtool/devtool.js create mode 100644 dist/creator/transformations/devtool/devtool.test.js create mode 100644 dist/creator/transformations/entry/__testfixtures__/entry-0.input.js create mode 100644 dist/creator/transformations/entry/entry.js create mode 100644 dist/creator/transformations/entry/entry.test.js create mode 100644 dist/creator/transformations/externals/__testfixtures__/externals-0.input.js create mode 100644 dist/creator/transformations/externals/__testfixtures__/externals-1.input.js create mode 100644 dist/creator/transformations/externals/externals.js create mode 100644 dist/creator/transformations/externals/externals.test.js create mode 100644 dist/creator/transformations/index.js create mode 100644 dist/creator/transformations/module/__testfixtures__/module-0.input.js create mode 100644 dist/creator/transformations/module/__testfixtures__/module-1.input.js create mode 100644 dist/creator/transformations/module/module.js create mode 100644 dist/creator/transformations/module/module.test.js create mode 100644 dist/creator/transformations/node/__testfixtures__/node-0.input.js create mode 100644 dist/creator/transformations/node/node.js create mode 100644 dist/creator/transformations/node/node.test.js create mode 100644 dist/creator/transformations/other/__testfixtures__/other-0.input.js create mode 100644 dist/creator/transformations/other/amd.js create mode 100644 dist/creator/transformations/other/bail.js create mode 100644 dist/creator/transformations/other/cache.js create mode 100644 dist/creator/transformations/other/merge.js create mode 100644 dist/creator/transformations/other/other.test.js create mode 100644 dist/creator/transformations/other/profile.js create mode 100644 dist/creator/transformations/output/__testfixtures__/output-0.input.js create mode 100644 dist/creator/transformations/output/output.js create mode 100644 dist/creator/transformations/output/output.test.js create mode 100644 dist/creator/transformations/performance/__testfixtures__/performance-0.input.js create mode 100644 dist/creator/transformations/performance/performance.js create mode 100644 dist/creator/transformations/performance/performance.test.js create mode 100644 dist/creator/transformations/plugins/__testfixtures__/plugins-0.input.js create mode 100644 dist/creator/transformations/plugins/plugins.js create mode 100644 dist/creator/transformations/plugins/plugins.test.js create mode 100644 dist/creator/transformations/resolve/__testfixtures__/resolve-0.input.js create mode 100644 dist/creator/transformations/resolve/resolve.js create mode 100644 dist/creator/transformations/resolve/resolve.test.js create mode 100644 dist/creator/transformations/stats/__testfixtures__/stats-0.input.js create mode 100644 dist/creator/transformations/stats/stats.js create mode 100644 dist/creator/transformations/stats/stats.test.js create mode 100644 dist/creator/transformations/target/__testfixtures__/target-0.input.js create mode 100644 dist/creator/transformations/target/__testfixtures__/target-1.input.js create mode 100644 dist/creator/transformations/target/target.js create mode 100644 dist/creator/transformations/target/target.test.js create mode 100644 dist/creator/transformations/top-scope/__testfixtures__/top-scope-0.input.js create mode 100644 dist/creator/transformations/top-scope/top-scope.js create mode 100644 dist/creator/transformations/top-scope/top-scope.test.js create mode 100644 dist/creator/transformations/watch/__testfixtures__/watch-0.input.js create mode 100644 dist/creator/transformations/watch/__testfixtures__/watch-1.input.js create mode 100644 dist/creator/transformations/watch/__testfixtures__/watch-2.input.js create mode 100644 dist/creator/transformations/watch/watch.js create mode 100644 dist/creator/transformations/watch/watch.test.js create mode 100644 dist/creator/transformations/watch/watchOptions.js create mode 100644 dist/creator/transformations/watch/watchOptions.test.js create mode 100644 dist/creator/utils/run-prettier.js create mode 100644 dist/creator/utils/validate-options.js create mode 100644 dist/creator/utils/validate-options.spec.js create mode 100644 dist/creator/yeoman/utils/entry.js create mode 100644 dist/creator/yeoman/utils/module.js create mode 100644 dist/creator/yeoman/utils/plugins.js create mode 100644 dist/creator/yeoman/utils/tooltip.js create mode 100644 dist/creator/yeoman/utils/validate.js create mode 100644 dist/creator/yeoman/webpack-adapter.js create mode 100644 dist/creator/yeoman/webpack-generator.js create mode 100644 dist/generate-loader/index.js create mode 100644 dist/generate-loader/loader-generator.js create mode 100644 dist/generate-loader/loader-generator.test.js create mode 100644 dist/generate-plugin/index.js create mode 100644 dist/generate-plugin/plugin-generator.js rename {lib => dist}/initialize.js (100%) create mode 100644 dist/migrate.js create mode 100644 dist/transformations/__testfixtures__/failing.js create mode 100644 dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-0.input.js create mode 100644 dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-1.input.js create mode 100644 dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-2.input.js create mode 100644 dist/transformations/bannerPlugin/bannerPlugin.js create mode 100644 dist/transformations/bannerPlugin/bannerPlugin.test.js create mode 100644 dist/transformations/defineTest.js create mode 100644 dist/transformations/extractTextPlugin/__testfixtures__/extractTextPlugin.input.js create mode 100644 dist/transformations/extractTextPlugin/extractTextPlugin.js create mode 100644 dist/transformations/extractTextPlugin/extractTextPlugin.test.js create mode 100644 dist/transformations/index.js create mode 100644 dist/transformations/index.test.js create mode 100644 dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-0.input.js create mode 100644 dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-1.input.js create mode 100644 dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-2.input.js create mode 100644 dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-3.input.js create mode 100644 dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js create mode 100644 dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.test.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-0.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-1.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-2.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-3.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-4.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-5.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-6.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-7.input.js create mode 100644 dist/transformations/loaders/__testfixtures__/loaders-8.input.js create mode 100644 dist/transformations/loaders/loaders.js create mode 100644 dist/transformations/loaders/loaders.test.js create mode 100644 dist/transformations/outputPath/__testfixtures__/outputPath-0.input.js create mode 100644 dist/transformations/outputPath/__testfixtures__/outputPath-1.input.js create mode 100644 dist/transformations/outputPath/__testfixtures__/outputPath-2.input.js create mode 100644 dist/transformations/outputPath/outputPath.js create mode 100644 dist/transformations/outputPath/outputPath.test.js create mode 100644 dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-0.input.js create mode 100644 dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-1.input.js create mode 100644 dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-2.input.js create mode 100644 dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-3.input.js create mode 100644 dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-4.input.js create mode 100644 dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js create mode 100644 dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js create mode 100644 dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-0.input.js create mode 100644 dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-1.input.js create mode 100644 dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-2.input.js create mode 100644 dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-3.input.js create mode 100644 dist/transformations/removeJsonLoader/removeJsonLoader.js create mode 100644 dist/transformations/removeJsonLoader/removeJsonLoader.test.js create mode 100644 dist/transformations/resolve/__testfixtures__/resolve.input.js create mode 100644 dist/transformations/resolve/resolve.js create mode 100644 dist/transformations/resolve/resolve.test.js create mode 100644 dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js create mode 100644 dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js create mode 100644 dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js create mode 100644 dist/transformations/uglifyJsPlugin/uglifyJsPlugin.js create mode 100644 dist/transformations/uglifyJsPlugin/uglifyJsPlugin.test.js create mode 100644 dist/transformations/utils.js create mode 100644 dist/transformations/utils.test.js create mode 100644 dist/types.js create mode 100644 dist/utils/WebpackOptionsValidationError.js create mode 100644 dist/utils/copy-utils.js create mode 100644 dist/utils/npm-exists.js create mode 100644 dist/utils/npm-exists.spec.js create mode 100644 dist/utils/npm-packages-exists.js create mode 100644 dist/utils/package-manager.js create mode 100644 dist/utils/package-manager.spec.js create mode 100644 dist/utils/resolve-packages.js create mode 100644 dist/utils/resolve-packages.spec.js create mode 100644 dist/utils/validateSchema.js create mode 100644 dist/utils/webpack-generator.js create mode 100644 lib/index.js create mode 100644 lib/init.js create mode 100644 lib/types.js diff --git a/.babelrc b/.babelrc index 11f1df83f71..36afa5aa994 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ { - "presets": ["es2015", "stage-3"] + "presets": ["es2015", "stage-3", "flow"] } diff --git a/.eslintignore b/.eslintignore index a62988d01bf..aa24f295da5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ **/__testfixtures__/* coverage test +dist diff --git a/.eslintrc.js b/.eslintrc.js index 4c2f4e927bd..c6380e19e03 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { "root": true, - "plugins": ["node"], - "extends": ["eslint:recommended", "plugin:node/recommended"], + "plugins": ["node", "flowtype"], + "extends": ["eslint:recommended", "plugin:node/recommended", "plugin:flowtype/recommended"], "env": { "node": true, "es6": true, diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 00000000000..a571c285c48 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,11 @@ +[ignore] + +[include] + +[libs] +flow-typed + +[options] +module.system.node.resolve_dirname=./lib/ +module.system.node.resolve_dirname=node_modules +experimental.const_params=false diff --git a/.gitignore b/.gitignore index 4dd5c95c1b5..b6ec97854f4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,8 @@ yarn-error.log # Jest Coverage coverage +# Distribution Build +dist + # Test Compilation test/js/* diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000000..e5b2e46fb55 --- /dev/null +++ b/.npmignore @@ -0,0 +1,15 @@ +test +coverage +lib +.travis.yml +.eslintrc* +.fitcommitjsrc.json +.vscode +.editorconfig +.eslintignore +__mocks__ +__testfixtures__ +*.test.js +*.input.js +types.js +*.spec.js diff --git a/.travis.yml b/.travis.yml index 807f3a1f60b..4e493cc75ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,5 +17,6 @@ before_script: - bash <(curl -s https://codecov.io/bash) script: + - npm run prepublish - npm run lint - npm run test diff --git a/bin/webpack.js b/bin/webpack.js index e9a711773cc..3520b6bb4cf 100755 --- a/bin/webpack.js +++ b/bin/webpack.js @@ -9,24 +9,20 @@ var resolveCwd = require("resolve-cwd"); // Local version replace global one var localCLI = resolveCwd.silent("webpack-cli/bin/webpack"); -if (process.argv.slice(2).indexOf("init") >= 0) { - var initPkgs = - process.argv.slice(2).length === 1 ? [] : [process.argv.slice(2).pop()]; - //eslint-disable-next-line - return require("../lib/initialize")(initPkgs); -} else if (process.argv.slice(2).indexOf("migrate") >= 0) { - var filePaths = - process.argv.slice(2).length === 1 ? [] : [process.argv.slice(2).pop()]; - if (!filePaths.length) { - throw new Error("Please specify a path to your webpack config"); - } - var inputConfigPath = path.resolve(process.cwd(), filePaths[0]); - //eslint-disable-next-line - return require("../lib/migrate.js")(inputConfigPath, inputConfigPath); -} else if (process.argv.slice(2).indexOf("generate-loader") >= 0) { - return require("../lib/generate-loader/index.js")(); -} else if (process.argv.slice(2).indexOf("generate-plugin") >= 0) { - return require("../lib/generate-plugin/index.js")(); +const NON_COMPILATION_ARGS = [ + "init", + "migrate", + "generate-loader", + "generate-plugin" +]; + +const NON_COMPILATION_CMD = process.argv.find(arg => { + return NON_COMPILATION_ARGS.find(a => a === arg); +}); + +if (NON_COMPILATION_CMD) { + // eslint-disable-next-line + return require("../dist/index")(NON_COMPILATION_CMD, process.argv); } if (localCLI && path.relative(localCLI, __filename) !== "") { diff --git a/dist/creator/index.js b/dist/creator/index.js new file mode 100644 index 00000000000..e68049c963a --- /dev/null +++ b/dist/creator/index.js @@ -0,0 +1,66 @@ +"use strict"; + +const yeoman = require("yeoman-environment"); +const Generator = require("yeoman-generator"); +const path = require("path"); +const defaultGenerator = require("./yeoman/webpack-generator"); +const WebpackAdapter = require("./yeoman/webpack-adapter"); +const runTransform = require("./transformations/index"); + +/* +* @function creator +* +* Runs yeoman and runs the transformations based on the object +* built up from an author/user +* +* @param { String } options - An path to the given generator +* @returns { Function } runTransform - Run transformations based on yeoman prompt +*/ + +function creator(options) { + let env = yeoman.createEnv("webpack", null, new WebpackAdapter()); + const generatorName = options + ? replaceGeneratorName(path.basename(options[0])) + : "webpack-default-generator"; + if (options) { + const WebpackGenerator = class extends Generator { + initializing() { + options.forEach(path => { + return this.composeWith(require.resolve(path)); + }); + } + }; + env.registerStub(WebpackGenerator, generatorName); + } else { + env.registerStub(defaultGenerator, "webpack-default-generator"); + } + + env.run(generatorName).on("end", () => { + if (generatorName !== "webpack-default-generator") { + //HACK / FIXME + env = env.options.env; + return runTransform(env.configuration); + } else { + return runTransform(env.getArgument("configuration")); + } + }); +} + +/* +* @function replaceGeneratorName +* +* Replaces the webpack-addons pattern with the end of the addons name merged +* with 'generator' +* +* @param { String } name - name of the generator +* @returns { String } name - replaced pattern of the name +*/ + +function replaceGeneratorName(name) { + return name.replace(/(webpack-addons)?([^:]+)(:.*)?/g, "generator$2"); +} + +module.exports = { + creator, + replaceGeneratorName +}; diff --git a/dist/creator/index.test.js b/dist/creator/index.test.js new file mode 100644 index 00000000000..a02620e312a --- /dev/null +++ b/dist/creator/index.test.js @@ -0,0 +1,10 @@ +"use strict"; + +const replaceGeneratorName = require("./index").replaceGeneratorName; + +describe("replaceGeneratorName", () => { + it("should replace a pattern of an addon", () => { + const generatorName = replaceGeneratorName("webpack-addons-thefox"); + expect(generatorName).toEqual("generator-thefox"); + }); +}); diff --git a/dist/creator/transformations/context/__testfixtures__/context-0.input.js b/dist/creator/transformations/context/__testfixtures__/context-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/context/__testfixtures__/context-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/context/__testfixtures__/context-1.input.js b/dist/creator/transformations/context/__testfixtures__/context-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/context/__testfixtures__/context-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/context/__testfixtures__/context-2.input.js b/dist/creator/transformations/context/__testfixtures__/context-2.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/context/__testfixtures__/context-2.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/context/context.js b/dist/creator/transformations/context/context.js new file mode 100644 index 00000000000..15926ce0aef --- /dev/null +++ b/dist/creator/transformations/context/context.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for context. Finds the context property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "context", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/context/context.test.js b/dist/creator/transformations/context/context.test.js new file mode 100644 index 00000000000..d21b1d8dec5 --- /dev/null +++ b/dist/creator/transformations/context/context.test.js @@ -0,0 +1,7 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "context", "context-0", "path.resolve(__dirname, 'app')"); +defineTest(__dirname, "context", "context-1", "'./some/fake/path'"); +defineTest(__dirname, "context", "context-2", "contextVariable"); diff --git a/dist/creator/transformations/devServer/__testfixtures__/devServer-0.input.js b/dist/creator/transformations/devServer/__testfixtures__/devServer-0.input.js new file mode 100644 index 00000000000..080e440f373 --- /dev/null +++ b/dist/creator/transformations/devServer/__testfixtures__/devServer-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + }, +} diff --git a/dist/creator/transformations/devServer/__testfixtures__/devServer-1.input.js b/dist/creator/transformations/devServer/__testfixtures__/devServer-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/devServer/__testfixtures__/devServer-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/devServer/devServer.js b/dist/creator/transformations/devServer/devServer.js new file mode 100644 index 00000000000..14d6b765b5a --- /dev/null +++ b/dist/creator/transformations/devServer/devServer.js @@ -0,0 +1,40 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for devServer. Finds the devServer property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createDevServerProperty(p) { + utils.pushCreateProperty(j, p, "devServer", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "devServer"); + } + if (webpackProperties && typeof webpackProperties === "object") { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createDevServerProperty)); + } else if (webpackProperties && webpackProperties.length) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "devServer", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/devServer/devServer.test.js b/dist/creator/transformations/devServer/devServer.test.js new file mode 100644 index 00000000000..14b3b7d11de --- /dev/null +++ b/dist/creator/transformations/devServer/devServer.test.js @@ -0,0 +1,10 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "devServer", "devServer-0", { + contentBase: "path.join(__dirname, 'dist')", + compress: true, + port: 9000 +}); +defineTest(__dirname, "devServer", "devServer-1", "someVar"); diff --git a/dist/creator/transformations/devtool/__testfixtures__/devtool-0.input.js b/dist/creator/transformations/devtool/__testfixtures__/devtool-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/devtool/__testfixtures__/devtool-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/devtool/__testfixtures__/devtool-1.input.js b/dist/creator/transformations/devtool/__testfixtures__/devtool-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/devtool/__testfixtures__/devtool-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/devtool/devtool.js b/dist/creator/transformations/devtool/devtool.js new file mode 100644 index 00000000000..87cf628dc41 --- /dev/null +++ b/dist/creator/transformations/devtool/devtool.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for devtool. Finds the devtool property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "devtool", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/devtool/devtool.test.js b/dist/creator/transformations/devtool/devtool.test.js new file mode 100644 index 00000000000..8702e338922 --- /dev/null +++ b/dist/creator/transformations/devtool/devtool.test.js @@ -0,0 +1,8 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "devtool", "devtool-0", "'source-map'"); +defineTest(__dirname, "devtool", "devtool-0", "myVariable"); +defineTest(__dirname, "devtool", "devtool-1", "'cheap-module-source-map'"); +defineTest(__dirname, "devtool", "devtool-1", "false"); diff --git a/dist/creator/transformations/entry/__testfixtures__/entry-0.input.js b/dist/creator/transformations/entry/__testfixtures__/entry-0.input.js new file mode 100644 index 00000000000..4ba52ba2c8d --- /dev/null +++ b/dist/creator/transformations/entry/__testfixtures__/entry-0.input.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/dist/creator/transformations/entry/entry.js b/dist/creator/transformations/entry/entry.js new file mode 100644 index 00000000000..8b0970f0eef --- /dev/null +++ b/dist/creator/transformations/entry/entry.js @@ -0,0 +1,41 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for entry. Finds the entry property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createEntryProperty(p) { + if (typeof webpackProperties === "string") { + return utils.pushCreateProperty(j, p, "entry", webpackProperties); + } + if (Array.isArray(webpackProperties)) { + const externalArray = utils.createArrayWithChildren( + j, + "entry", + webpackProperties, + true + ); + return p.value.properties.push(externalArray); + } else { + utils.pushCreateProperty(j, p, "entry", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "entry"); + } + } + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createEntryProperty)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/entry/entry.test.js b/dist/creator/transformations/entry/entry.test.js new file mode 100644 index 00000000000..1124378369a --- /dev/null +++ b/dist/creator/transformations/entry/entry.test.js @@ -0,0 +1,24 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "entry", "entry-0", "'index.js'"); +defineTest(__dirname, "entry", "entry-0", ["'index.js'", "'app.js'"]); +defineTest(__dirname, "entry", "entry-0", { + index: "'index.js'", + app: "'app.js'" +}); + +defineTest(__dirname, "entry", "entry-0", { + inject: "something", + app: "'app.js'", + inject_1: "else" +}); +defineTest(__dirname, "entry", "entry-0", "() => 'index.js'"); +defineTest( + __dirname, + "entry", + "entry-0", + "() => new Promise((resolve) => resolve(['./app', './router']))" +); +defineTest(__dirname, "entry", "entry-0", "entryStringVariable"); diff --git a/dist/creator/transformations/externals/__testfixtures__/externals-0.input.js b/dist/creator/transformations/externals/__testfixtures__/externals-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/externals/__testfixtures__/externals-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/externals/__testfixtures__/externals-1.input.js b/dist/creator/transformations/externals/__testfixtures__/externals-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/externals/__testfixtures__/externals-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/externals/externals.js b/dist/creator/transformations/externals/externals.js new file mode 100644 index 00000000000..29b394a1e37 --- /dev/null +++ b/dist/creator/transformations/externals/externals.js @@ -0,0 +1,54 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for externals. Finds the externals property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createExternalProperty(p) { + if ( + webpackProperties instanceof RegExp || + typeof webpackProperties === "string" + ) { + return utils.pushCreateProperty(j, p, "externals", webpackProperties); + } + if (Array.isArray(webpackProperties)) { + const externalArray = utils.createArrayWithChildren( + j, + "externals", + webpackProperties, + true + ); + return p.value.properties.push(externalArray); + } else { + utils.pushCreateProperty(j, p, "externals", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "externals"); + } + } + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter( + p => + utils.safeTraverse(p, [ + "parent", + "value", + "left", + "property", + "name" + ]) === "exports" + ) + .forEach(createExternalProperty); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/externals/externals.test.js b/dist/creator/transformations/externals/externals.test.js new file mode 100644 index 00000000000..a64bceaef78 --- /dev/null +++ b/dist/creator/transformations/externals/externals.test.js @@ -0,0 +1,61 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "externals", "externals-0", /react/); +defineTest(__dirname, "externals", "externals-1", { + jquery: "'jQuery'", + react: "'react'" +}); + +defineTest(__dirname, "externals", "externals-1", "myObj"); + +defineTest(__dirname, "externals", "externals-1", { + jquery: "'jQuery'", + react: "reactObj" +}); + +defineTest(__dirname, "externals", "externals-1", { + jquery: "'jQuery'", + react: ["reactObj", "path.join(__dirname, 'app')", "'jquery'"] +}); + +defineTest(__dirname, "externals", "externals-1", { + lodash: { + commonjs: "'lodash'", + amd: "'lodash'", + root: "'_'" + } +}); + +defineTest(__dirname, "externals", "externals-1", { + lodash: { + commonjs: "lodash", + amd: "hidash", + root: "_" + } +}); + +defineTest(__dirname, "externals", "externals-1", [ + { + a: "false", + b: "true", + "'./ext'": "./hey" + }, + "function(context, request, callback) {" + + "if (/^yourregex$/.test(request)){" + + "return callback(null, 'commonjs ' + request);" + + "}" + + "callback();" + + "}" +]); + +defineTest(__dirname, "externals", "externals-1", [ + "myObj", + "function(context, request, callback) {" + + "if (/^yourregex$/.test(request)){" + + "return callback(null, 'commonjs ' + request);" + + "}" + + "callback();" + + "}" +]); diff --git a/dist/creator/transformations/index.js b/dist/creator/transformations/index.js new file mode 100644 index 00000000000..5768a743ba9 --- /dev/null +++ b/dist/creator/transformations/index.js @@ -0,0 +1,118 @@ +"use strict"; + +const path = require("path"); +const j = require("jscodeshift"); +const chalk = require("chalk"); +const pEachSeries = require("p-each-series"); + +const runPrettier = require("../utils/run-prettier"); + +const entryTransform = require("./entry/entry"); +const outputTransform = require("./output/output"); +const contextTransform = require("./context/context"); +const resolveTransform = require("./resolve/resolve"); +const devtoolTransform = require("./devtool/devtool"); +const targetTransform = require("./target/target"); +const watchTransform = require("./watch/watch"); +const watchOptionsTransform = require("./watch/watchOptions"); +const externalsTransform = require("./externals/externals"); +const nodeTransform = require("./node/node"); +const performanceTransform = require("./performance/performance"); +const statsTransform = require("./stats/stats"); +const amdTransform = require("./other/amd"); +const bailTransform = require("./other/bail"); +const cacheTransform = require("./other/cache"); +const profileTransform = require("./other/profile"); +const mergeTransform = require("./other/merge"); +const moduleTransform = require("./module/module"); +const pluginsTransform = require("./plugins/plugins"); +const topScopeTransform = require("./top-scope/top-scope"); +const devServerTransform = require("./devServer/devServer"); + +/* +* @function runTransform +* +* Runs the transformations from an object we get from yeoman +* +* @param { Object } transformObject - Options to transform +* @returns { } - A promise that writes each transform, runs prettier +* and writes the file +*/ + +const transformsObject = { + entryTransform, + outputTransform, + contextTransform, + resolveTransform, + devtoolTransform, + targetTransform, + watchTransform, + watchOptionsTransform, + externalsTransform, + nodeTransform, + performanceTransform, + statsTransform, + amdTransform, + bailTransform, + cacheTransform, + profileTransform, + moduleTransform, + pluginsTransform, + topScopeTransform, + mergeTransform, + devServerTransform +}; + +module.exports = function runTransform(webpackProperties) { + // webpackOptions.name sent to nameTransform if match + Object.keys(webpackProperties).forEach(scaffoldPiece => { + const config = webpackProperties[scaffoldPiece]; + + const transformations = Object.keys(transformsObject).map(k => { + const stringVal = k.substr(0, k.indexOf("Transform")); + if (config.webpackOptions) { + if (config.webpackOptions[stringVal]) { + return [transformsObject[k], config.webpackOptions[stringVal]]; + } else { + return [transformsObject[k], config[stringVal]]; + } + } else { + return [transformsObject[k]]; + } + }); + + const ast = j("module.exports = {}"); + + return pEachSeries(transformations, f => { + if (!f[1]) { + return f[0](j, ast); + } else { + return f[0](j, ast, f[1]); + } + }) + .then(() => { + let configurationName; + if (!config.configName) { + configurationName = "webpack.config.js"; + } else { + configurationName = "webpack." + config.configName + ".js"; + } + + const outputPath = path.join(process.cwd(), configurationName); + const source = ast.toSource({ + quote: "single" + }); + + runPrettier(outputPath, source); + }) + .catch(err => { + console.error(err.message ? err.message : err); + }); + }); + process.stdout.write( + "\n" + + chalk.green( + "Congratulations! Your new webpack configuration file has been created!\n" + ) + ); +}; diff --git a/dist/creator/transformations/module/__testfixtures__/module-0.input.js b/dist/creator/transformations/module/__testfixtures__/module-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/module/__testfixtures__/module-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/module/__testfixtures__/module-1.input.js b/dist/creator/transformations/module/__testfixtures__/module-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/module/__testfixtures__/module-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/module/module.js b/dist/creator/transformations/module/module.js new file mode 100644 index 00000000000..991a7497e8a --- /dev/null +++ b/dist/creator/transformations/module/module.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for module. Finds the module property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createModuleProperties(p) { + utils.pushCreateProperty(j, p, "module", j.objectExpression([])); + return utils.safeTraverse(p, ["key", "name"] === "module"); + } + function createRules(p) { + return utils.pushObjectKeys(j, p, webpackProperties, "module"); + } + if (!webpackProperties) { + return ast; + } else { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createModuleProperties)) + .forEach(p => createRules(p)); + } +}; diff --git a/dist/creator/transformations/module/module.test.js b/dist/creator/transformations/module/module.test.js new file mode 100644 index 00000000000..278f246c28f --- /dev/null +++ b/dist/creator/transformations/module/module.test.js @@ -0,0 +1,108 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "module", "module-0", { + rules: [ + { + test: new RegExp(/\.(js|vue)$/), + loader: "'eslint-loader'", + enforce: "'pre'", + include: ["customObj", "'Stringy'"], + options: { + formatter: "'someOption'" + } + }, + { + test: new RegExp(/\.vue$/), + loader: "'vue-loader'", + options: "vueObject" + }, + { + test: new RegExp(/\.js$/), + loader: "'babel-loader'", + include: ["resolve('src')", "resolve('test')"] + }, + { + test: new RegExp(/\.(png|jpe?g|gif|svg)(\?.*)?$/), + loader: "'url-loader'", + options: { + limit: 10000, + name: "utils.assetsPath('img/[name].[hash:7].[ext]')" + } + }, + { + test: new RegExp(/\.(woff2?|eot|ttf|otf)(\?.*)?$/), + loader: "'url-loader'", + options: { + limit: "10000", + name: "utils.assetsPath('fonts/[name].[hash:7].[ext]')", + someArr: ["Hey"] + } + } + ] +}); + +defineTest(__dirname, "module", "module-1", { + noParse: /jquery|lodash/, + rules: [ + { + test: new RegExp(/\.js$/), + parser: { + amd: false + }, + use: [ + "'htmllint-loader'", + { + loader: "'html-loader'", + options: { + hello: "'world'" + } + } + ] + } + ] +}); + +defineTest(__dirname, "module", "module-0", { + rules: [ + "{{#if_eq build 'standalone'}}", + { + test: new RegExp(/\.(js|vue)$/), + loader: "'eslint-loader'", + enforce: "'pre'", + include: ["customObj", "'Stringy'"], + options: { + formatter: "'someOption'" + } + }, + { + test: new RegExp(/\.vue$/), + loader: "'vue-loader'", + options: "vueObject" + }, + { + test: new RegExp(/\.js$/), + loader: "'babel-loader'", + include: ["resolve('src')", "resolve('test')"] + }, + { + test: new RegExp(/\.(png|jpe?g|gif|svg)(\?.*)?$/), + loader: "'url-loader'", + options: { + limit: 10000, + name: "utils.assetsPath('img/[name].[hash:7].[ext]')", + inject: "{{#if_eq build 'standalone'}}" + } + }, + { + test: new RegExp(/\.(woff2?|eot|ttf|otf)(\?.*)?$/), + loader: "'url-loader'", + inject: "{{#if_eq build 'standalone'}}", + options: { + limit: "10000", + name: "utils.assetsPath('fonts/[name].[hash:7].[ext]')" + } + } + ] +}); diff --git a/dist/creator/transformations/node/__testfixtures__/node-0.input.js b/dist/creator/transformations/node/__testfixtures__/node-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/node/__testfixtures__/node-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/node/node.js b/dist/creator/transformations/node/node.js new file mode 100644 index 00000000000..92a3d31a271 --- /dev/null +++ b/dist/creator/transformations/node/node.js @@ -0,0 +1,28 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for node. Finds the node property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createNodeProperty(p) { + utils.pushCreateProperty(j, p, "node", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "node"); + } + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createNodeProperty)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/node/node.test.js b/dist/creator/transformations/node/node.test.js new file mode 100644 index 00000000000..bfa478d9e19 --- /dev/null +++ b/dist/creator/transformations/node/node.test.js @@ -0,0 +1,13 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "node", "node-0", { + console: false, + global: true, + process: true, + Buffer: true, + __filename: "mock", + __dirname: "mock", + setImmediate: true +}); diff --git a/dist/creator/transformations/other/__testfixtures__/other-0.input.js b/dist/creator/transformations/other/__testfixtures__/other-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/other/__testfixtures__/other-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/other/amd.js b/dist/creator/transformations/other/amd.js new file mode 100644 index 00000000000..24d0aa9a24e --- /dev/null +++ b/dist/creator/transformations/other/amd.js @@ -0,0 +1,28 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for amd. Finds the amd property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createAMDProperty(p) { + utils.pushCreateProperty(j, p, "amd", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "amd"); + } + if (webpackProperties && typeof webpackProperties === "object") { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createAMDProperty)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/other/bail.js b/dist/creator/transformations/other/bail.js new file mode 100644 index 00000000000..1fe4f3bed37 --- /dev/null +++ b/dist/creator/transformations/other/bail.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for bail. Finds the bail property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "bail", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/other/cache.js b/dist/creator/transformations/other/cache.js new file mode 100644 index 00000000000..ecb9eae8d72 --- /dev/null +++ b/dist/creator/transformations/other/cache.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for cache. Finds the cache property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "cache", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/other/merge.js b/dist/creator/transformations/other/merge.js new file mode 100644 index 00000000000..5a80331de5d --- /dev/null +++ b/dist/creator/transformations/other/merge.js @@ -0,0 +1,46 @@ +"use strict"; + +/* +* +* Transform for merge. Finds the merge property from yeoman and creates a way +* for users to allow webpack-merge in their scaffold +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createMergeProperty(p) { + // FIXME Use j.callExp() + let exportsDecl = p.value.body.map(n => { + if (n.expression) { + return n.expression.right; + } + }); + const bodyLength = exportsDecl.length; + let newVal = {}; + newVal.type = "ExpressionStatement"; + newVal.expression = { + type: "AssignmentExpression", + operator: "=", + left: { + type: "MemberExpression", + computed: false, + object: j.identifier("module"), + property: j.identifier("exports") + }, + right: j.callExpression(j.identifier("merge"), [ + j.identifier(webpackProperties), + exportsDecl.pop() + ]) + }; + p.value.body[bodyLength - 1] = newVal; + } + if (webpackProperties) { + return ast.find(j.Program).filter(p => createMergeProperty(p)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/other/other.test.js b/dist/creator/transformations/other/other.test.js new file mode 100644 index 00000000000..a976a9ba25f --- /dev/null +++ b/dist/creator/transformations/other/other.test.js @@ -0,0 +1,13 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "amd", "other-0", { + jQuery: true, + kQuery: false +}); +defineTest(__dirname, "bail", "other-0", true); +defineTest(__dirname, "cache", "other-0", true); +defineTest(__dirname, "cache", "other-0", "cacheVal"); +defineTest(__dirname, "profile", "other-0", true); +defineTest(__dirname, "merge", "other-0", "myConfig"); diff --git a/dist/creator/transformations/other/profile.js b/dist/creator/transformations/other/profile.js new file mode 100644 index 00000000000..07f08bea4f7 --- /dev/null +++ b/dist/creator/transformations/other/profile.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for profile. Finds the profile property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "profile", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/output/__testfixtures__/output-0.input.js b/dist/creator/transformations/output/__testfixtures__/output-0.input.js new file mode 100644 index 00000000000..a9899df14fa --- /dev/null +++ b/dist/creator/transformations/output/__testfixtures__/output-0.input.js @@ -0,0 +1,3 @@ +module.exports = { + entry: 'index.js' +} diff --git a/dist/creator/transformations/output/output.js b/dist/creator/transformations/output/output.js new file mode 100644 index 00000000000..1c19f70d095 --- /dev/null +++ b/dist/creator/transformations/output/output.js @@ -0,0 +1,27 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for output. Finds the output property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ +module.exports = function(j, ast, webpackProperties) { + function createOutputProperties(p) { + utils.pushCreateProperty(j, p, "output", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "output"); + } + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createOutputProperties)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/output/output.test.js b/dist/creator/transformations/output/output.test.js new file mode 100644 index 00000000000..9b7544604b8 --- /dev/null +++ b/dist/creator/transformations/output/output.test.js @@ -0,0 +1,15 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); +const jscodeshift = require("jscodeshift"); + +defineTest(__dirname, "output", "output-0", { + filename: "'bundle'", + path: "'dist/assets'", + pathinfo: true, + publicPath: "'https://google.com'", + sourceMapFilename: "'[name].map'", + sourcePrefix: jscodeshift("'\t'"), + umdNamedDefine: true, + strictModuleExceptionHandling: true +}); diff --git a/dist/creator/transformations/performance/__testfixtures__/performance-0.input.js b/dist/creator/transformations/performance/__testfixtures__/performance-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/performance/__testfixtures__/performance-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/performance/performance.js b/dist/creator/transformations/performance/performance.js new file mode 100644 index 00000000000..f00c1e21572 --- /dev/null +++ b/dist/creator/transformations/performance/performance.js @@ -0,0 +1,28 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for performance. Finds the performance property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createPerformanceProperty(p) { + utils.pushCreateProperty(j, p, "performance", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "performance"); + } + if (webpackProperties && typeof webpackProperties === "object") { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createPerformanceProperty)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/performance/performance.test.js b/dist/creator/transformations/performance/performance.test.js new file mode 100644 index 00000000000..61840ca16ff --- /dev/null +++ b/dist/creator/transformations/performance/performance.test.js @@ -0,0 +1,11 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "performance", "performance-0", { + hints: "'warning'", + maxEntrypointSize: 400000, + maxAssetSize: 100000, + assetFilter: + "function(assetFilename) {" + "return assetFilename.endsWith('.js');" + "}" +}); diff --git a/dist/creator/transformations/plugins/__testfixtures__/plugins-0.input.js b/dist/creator/transformations/plugins/__testfixtures__/plugins-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/plugins/__testfixtures__/plugins-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/plugins/plugins.js b/dist/creator/transformations/plugins/plugins.js new file mode 100644 index 00000000000..45c382501f1 --- /dev/null +++ b/dist/creator/transformations/plugins/plugins.js @@ -0,0 +1,33 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for plugins. Finds the plugins property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createPluginsProperty(p) { + const pluginArray = utils.createArrayWithChildren( + j, + "plugins", + webpackProperties, + true + ); + return p.value.properties.push(pluginArray); + } + if (webpackProperties && Array.isArray(webpackProperties)) { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createPluginsProperty)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/plugins/plugins.test.js b/dist/creator/transformations/plugins/plugins.test.js new file mode 100644 index 00000000000..7412024992f --- /dev/null +++ b/dist/creator/transformations/plugins/plugins.test.js @@ -0,0 +1,14 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "plugins", "plugins-0", [ + "new webpack.optimize.CommonsChunkPlugin({name:" + + "'" + + "vendor" + + "'" + + ",filename:" + + "'" + + "vendor" + + "-[hash].min.js'})" +]); diff --git a/dist/creator/transformations/resolve/__testfixtures__/resolve-0.input.js b/dist/creator/transformations/resolve/__testfixtures__/resolve-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/resolve/__testfixtures__/resolve-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/resolve/resolve.js b/dist/creator/transformations/resolve/resolve.js new file mode 100644 index 00000000000..01bba7000e3 --- /dev/null +++ b/dist/creator/transformations/resolve/resolve.js @@ -0,0 +1,28 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for resolve. Finds the resolve property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createResolveProperties(p) { + utils.pushCreateProperty(j, p, "resolve", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "resolve"); + } + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createResolveProperties)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/resolve/resolve.test.js b/dist/creator/transformations/resolve/resolve.test.js new file mode 100644 index 00000000000..89ecd087957 --- /dev/null +++ b/dist/creator/transformations/resolve/resolve.test.js @@ -0,0 +1,29 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "resolve", "resolve-0", { + alias: { + inject: "{{#if_eq build 'standalone'}}", + hello: "'world'", + inject_1: "{{/if_eq}}", + world: "hello" + }, + aliasFields: ["'browser'", "wars"], + descriptionFiles: ["'a'", "b"], + enforceExtension: false, + enforceModuleExtension: false, + extensions: ["hey", "'ho'"], + mainFields: ["main", "'story'"], + mainFiles: ["'noMainFileHere'", "iGuess"], + modules: ["one", "'two'"], + unsafeCache: false, + resolveLoader: { + modules: ["'node_modules'", "mode_nodules"], + extensions: ["jsVal", "'.json'"], + mainFields: ["loader", "'main'"], + moduleExtensions: ["'-loader'", "value"] + }, + plugins: ["somePlugin", "'stringVal'"], + symlinks: true +}); diff --git a/dist/creator/transformations/stats/__testfixtures__/stats-0.input.js b/dist/creator/transformations/stats/__testfixtures__/stats-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/stats/__testfixtures__/stats-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/stats/stats.js b/dist/creator/transformations/stats/stats.js new file mode 100644 index 00000000000..d843caf04ee --- /dev/null +++ b/dist/creator/transformations/stats/stats.js @@ -0,0 +1,40 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for stats. Finds the stats property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createStatsProperty(p) { + utils.pushCreateProperty(j, p, "stats", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "stats"); + } + if (webpackProperties && typeof webpackProperties === "object") { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createStatsProperty)); + } else if (webpackProperties && webpackProperties.length) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "stats", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/stats/stats.test.js b/dist/creator/transformations/stats/stats.test.js new file mode 100644 index 00000000000..c830ddb38e2 --- /dev/null +++ b/dist/creator/transformations/stats/stats.test.js @@ -0,0 +1,36 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "stats", "stats-0", { + assets: true, + assetsSort: "'field'", + cached: true, + cachedAssets: true, + children: true, + chunks: true, + chunkModules: true, + chunkOrigins: true, + chunksSort: "'field'", + context: "'../src/'", + colors: true, + depth: false, + entrypoints: "customVal", + errors: true, + errorDetails: true, + exclude: [], + hash: true, + maxModules: 15, + modules: true, + modulesSort: "'field'", + performance: true, + providedExports: false, + publicPath: true, + reasons: true, + source: true, + timings: true, + usedExports: false, + version: true, + warnings: true +}); +defineTest(__dirname, "stats", "stats-0", "'errors-only'"); diff --git a/dist/creator/transformations/target/__testfixtures__/target-0.input.js b/dist/creator/transformations/target/__testfixtures__/target-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/target/__testfixtures__/target-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/target/__testfixtures__/target-1.input.js b/dist/creator/transformations/target/__testfixtures__/target-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/target/__testfixtures__/target-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/target/target.js b/dist/creator/transformations/target/target.js new file mode 100644 index 00000000000..7b5dca6bed1 --- /dev/null +++ b/dist/creator/transformations/target/target.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for target. Finds the target property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (webpackProperties && webpackProperties.length) { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "target", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/target/target.test.js b/dist/creator/transformations/target/target.test.js new file mode 100644 index 00000000000..e29d6ab1853 --- /dev/null +++ b/dist/creator/transformations/target/target.test.js @@ -0,0 +1,6 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "target", "target-0", "'async-node'"); +defineTest(__dirname, "target", "target-1", "node"); diff --git a/dist/creator/transformations/top-scope/__testfixtures__/top-scope-0.input.js b/dist/creator/transformations/top-scope/__testfixtures__/top-scope-0.input.js new file mode 100644 index 00000000000..4ba52ba2c8d --- /dev/null +++ b/dist/creator/transformations/top-scope/__testfixtures__/top-scope-0.input.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/dist/creator/transformations/top-scope/top-scope.js b/dist/creator/transformations/top-scope/top-scope.js new file mode 100644 index 00000000000..daa92a75642 --- /dev/null +++ b/dist/creator/transformations/top-scope/top-scope.js @@ -0,0 +1,21 @@ +/* +* +* Get an property named topScope from yeoman and inject it to the top scope of +* the config, outside module.exports +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing topscope properties +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createTopScopeProperty(p) { + webpackProperties.forEach(n => { + p.value.body.splice(-1, 0, n); + }); + } + if (webpackProperties) { + return ast.find(j.Program).filter(p => createTopScopeProperty(p)); + } +}; diff --git a/dist/creator/transformations/top-scope/top-scope.test.js b/dist/creator/transformations/top-scope/top-scope.test.js new file mode 100644 index 00000000000..728b777c5d3 --- /dev/null +++ b/dist/creator/transformations/top-scope/top-scope.test.js @@ -0,0 +1,5 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "top-scope", "top-scope-0", ["var test = 'me';"]); diff --git a/dist/creator/transformations/watch/__testfixtures__/watch-0.input.js b/dist/creator/transformations/watch/__testfixtures__/watch-0.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/watch/__testfixtures__/watch-0.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/watch/__testfixtures__/watch-1.input.js b/dist/creator/transformations/watch/__testfixtures__/watch-1.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/watch/__testfixtures__/watch-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/watch/__testfixtures__/watch-2.input.js b/dist/creator/transformations/watch/__testfixtures__/watch-2.input.js new file mode 100644 index 00000000000..ea0822c2484 --- /dev/null +++ b/dist/creator/transformations/watch/__testfixtures__/watch-2.input.js @@ -0,0 +1,6 @@ +module.exports = { + entry: 'index.js', + output: { + filename: 'bundle.js' + } +} diff --git a/dist/creator/transformations/watch/watch.js b/dist/creator/transformations/watch/watch.js new file mode 100644 index 00000000000..ed92c572672 --- /dev/null +++ b/dist/creator/transformations/watch/watch.js @@ -0,0 +1,32 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for watch. Finds the watch property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + if (typeof webpackProperties === "boolean") { + return ast + .find(j.ObjectExpression) + .filter(p => + utils.isAssignment( + j, + p, + utils.pushCreateProperty, + "watch", + webpackProperties + ) + ); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/watch/watch.test.js b/dist/creator/transformations/watch/watch.test.js new file mode 100644 index 00000000000..ddaec086374 --- /dev/null +++ b/dist/creator/transformations/watch/watch.test.js @@ -0,0 +1,8 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "watch", "watch-0", true); +defineTest(__dirname, "watch", "watch-0", false); +defineTest(__dirname, "watch", "watch-1", true); +defineTest(__dirname, "watch", "watch-1", false); diff --git a/dist/creator/transformations/watch/watchOptions.js b/dist/creator/transformations/watch/watchOptions.js new file mode 100644 index 00000000000..c7f88cb48ab --- /dev/null +++ b/dist/creator/transformations/watch/watchOptions.js @@ -0,0 +1,28 @@ +"use strict"; + +const utils = require("../../../transformations/utils"); + +/* +* +* Transform for watchOptions. Finds the watchOptions property from yeoman and creates a +* property based on what the user has given us. +* +* @param j — jscodeshift API +* @param ast - jscodeshift API +* @param { Object } webpackProperties - Object containing transformation rules +* @returns ast - jscodeshift API +*/ + +module.exports = function(j, ast, webpackProperties) { + function createWatchOptionsProperty(p) { + utils.pushCreateProperty(j, p, "watchOptions", j.objectExpression([])); + return utils.pushObjectKeys(j, p, webpackProperties, "watchOptions"); + } + if (webpackProperties) { + return ast + .find(j.ObjectExpression) + .filter(p => utils.isAssignment(null, p, createWatchOptionsProperty)); + } else { + return ast; + } +}; diff --git a/dist/creator/transformations/watch/watchOptions.test.js b/dist/creator/transformations/watch/watchOptions.test.js new file mode 100644 index 00000000000..33a7d369fc3 --- /dev/null +++ b/dist/creator/transformations/watch/watchOptions.test.js @@ -0,0 +1,21 @@ +"use strict"; + +const defineTest = require("../../../transformations/defineTest"); + +defineTest(__dirname, "watchOptions", "watch-0", { + aggregateTimeout: 300, + poll: 1000, + ignored: "/node_modules/" +}); + +defineTest(__dirname, "watchOptions", "watch-1", { + aggregateTimeout: 300, + poll: 1000, + ignored: "/node_modules/" +}); + +defineTest(__dirname, "watchOptions", "watch-2", { + aggregateTimeout: 300, + poll: 1000, + ignored: "/node_modules/" +}); diff --git a/dist/creator/utils/run-prettier.js b/dist/creator/utils/run-prettier.js new file mode 100644 index 00000000000..5d503eedde0 --- /dev/null +++ b/dist/creator/utils/run-prettier.js @@ -0,0 +1,38 @@ +"use strict"; + +const prettier = require("prettier"); +const fs = require("fs"); +const chalk = require("chalk"); + +/* +* +* Runs prettier and later prints the output configuration +* +* @param { String } outputPath - Path to write the config to +* @param { Node } source - AST to write at the given path +* @returns fs - Writes a file at given location and prints messages accordingly +*/ + +module.exports = function runPrettier(outputPath, source) { + function validateConfig() { + let prettySource; + try { + prettySource = prettier.format(source, { + singleQuote: true, + useTabs: true, + tabWidth: 1 + }); + } catch (err) { + process.stdout.write( + "\n" + + chalk.yellow( + `WARNING: Could not apply prettier to ${outputPath}` + + " due validation error, but the file has been created\n" + ) + ); + prettySource = source; + } + return fs.writeFileSync(outputPath, prettySource, "utf8"); + } + return fs.writeFile(outputPath, source, "utf8", validateConfig); +}; diff --git a/dist/creator/utils/validate-options.js b/dist/creator/utils/validate-options.js new file mode 100644 index 00000000000..143e623f63e --- /dev/null +++ b/dist/creator/utils/validate-options.js @@ -0,0 +1,39 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +/* +* @function getPath +* +* Finds the current filepath of a given string +* +* @param { String } part - The name of the file to be checked. +* @returns { String } - returns an string with the filepath +*/ + +function getPath(part) { + return path.join(process.cwd(), part); +} + +/* +* @function validateOptions +* +* Validates the options passed from an inquirer instance to make +* sure the path supplied exists +* +* @param { String } part - The name of the file to be checked. +* @returns { } part - checks if the path exists or throws an error +*/ + +module.exports = function validateOptions(opts) { + return Object.keys(opts).forEach(location => { + let part = getPath(opts[location]); + try { + fs.readFileSync(part); + } catch (err) { + console.error("Found no file at:", part); + process.exitCode = 1; + } + }); +}; diff --git a/dist/creator/utils/validate-options.spec.js b/dist/creator/utils/validate-options.spec.js new file mode 100644 index 00000000000..fe342e78fa1 --- /dev/null +++ b/dist/creator/utils/validate-options.spec.js @@ -0,0 +1,25 @@ +"use strict"; + +"use strict"; + +const validateOptions = require("../../../__mocks__/creator/validate-options.mock") + .validateOptions; + +describe("validate-options", () => { + it("should throw on fake paths", () => { + expect(() => { + validateOptions({ + entry: "noop", + output: "noopsi" + }); + }).toThrowError("Did not find the file"); + }); + + it("should find the real files", () => { + expect(() => { + validateOptions({ + entry: "package.json" + }); + }).not.toThrowError(/'Did not find the file'/); + }); +}); diff --git a/dist/creator/yeoman/utils/entry.js b/dist/creator/yeoman/utils/entry.js new file mode 100644 index 00000000000..027499c9610 --- /dev/null +++ b/dist/creator/yeoman/utils/entry.js @@ -0,0 +1,81 @@ +"use strict"; + +const InputValidate = require("webpack-addons").InputValidate; +const validate = require("./validate"); + +module.exports = (self, answer) => { + let entryIdentifiers; + let result; + if (answer["entryType"] === true) { + result = self + .prompt([ + InputValidate( + "multipleEntries", + "Type the names you want for your modules (entry files), separated by comma [example: 'app,vendor']", + validate + ) + ]) + .then(multipleEntriesAnswer => { + let webpackEntryPoint = {}; + entryIdentifiers = multipleEntriesAnswer["multipleEntries"].split(","); + function forEachPromise(obj, fn) { + return obj.reduce(function(promise, prop) { + const trimmedProp = prop.trim(); + return promise.then(n => { + if (n) { + Object.keys(n).forEach(val => { + if ( + n[val].charAt(0) !== "(" && + n[val].charAt(0) !== "[" && + n[val].indexOf("function") < 0 && + n[val].indexOf("path") < 0 && + n[val].indexOf("process") < 0 + ) { + n[val] = `"${n[val]}.js"`; + } + webpackEntryPoint[val] = n[val]; + }); + } else { + n = {}; + } + return fn(trimmedProp); + }); + }, Promise.resolve()); + } + return forEachPromise(entryIdentifiers, entryProp => + self.prompt([ + InputValidate( + `${entryProp}`, + `What is the location of "${entryProp}"? [example: "./src/${entryProp}"]`, + validate + ) + ]) + ).then(propAns => { + Object.keys(propAns).forEach(val => { + if ( + propAns[val].charAt(0) !== "(" && + propAns[val].charAt(0) !== "[" && + propAns[val].indexOf("function") < 0 && + propAns[val].indexOf("path") < 0 && + propAns[val].indexOf("process") < 0 + ) { + propAns[val] = `"${propAns[val]}.js"`; + } + webpackEntryPoint[val] = propAns[val]; + }); + return webpackEntryPoint; + }); + }); + } else { + result = self + .prompt([ + InputValidate( + "singularEntry", + "Which module will be the first to enter the application? [example: './src/index']", + validate + ) + ]) + .then(singularAnswer => `"${singularAnswer["singularEntry"]}"`); + } + return result; +}; diff --git a/dist/creator/yeoman/utils/module.js b/dist/creator/yeoman/utils/module.js new file mode 100644 index 00000000000..b9b8660f606 --- /dev/null +++ b/dist/creator/yeoman/utils/module.js @@ -0,0 +1,10 @@ +module.exports = () => { + return { + test: new RegExp(/\.js$/), + exclude: "/node_modules/", + loader: "'babel-loader'", + options: { + presets: ["'es2015'"] + } + }; +}; diff --git a/dist/creator/yeoman/utils/plugins.js b/dist/creator/yeoman/utils/plugins.js new file mode 100644 index 00000000000..bcd523ad462 --- /dev/null +++ b/dist/creator/yeoman/utils/plugins.js @@ -0,0 +1,3 @@ +module.exports = () => { + return ["new UglifyJSPlugin()"]; +}; diff --git a/dist/creator/yeoman/utils/tooltip.js b/dist/creator/yeoman/utils/tooltip.js new file mode 100644 index 00000000000..97bf748a009 --- /dev/null +++ b/dist/creator/yeoman/utils/tooltip.js @@ -0,0 +1,47 @@ +module.exports = { + uglify: () => { + return `/* + * We've enabled UglifyJSPlugin for you! This minifies your app + * in order to load faster and run less javascript. + * + * https://github.com/webpack-contrib/uglifyjs-webpack-plugin + * + */`; + }, + commonsChunk: () => { + return `/* + * We've enabled commonsChunkPlugin for you. This allows your app to + * load faster and it splits the modules you provided as entries across + * different bundles! + * + * https://webpack.js.org/plugins/commons-chunk-plugin/ + * + */`; + }, + cssPlugin: () => { + return `/* + * We've enabled ExtractTextPlugin for you. This allows your app to + * use css modules that will be moved into a separate CSS file instead of inside + * one of your module entries! + * + * https://github.com/webpack-contrib/extract-text-webpack-plugin + * + */`; + }, + postcss: () => { + return `/* + * We've enabled Postcss, autoprefixer and precss for you. This allows your app + * to lint CSS, support variables and mixins, transpile future CSS syntax, + * inline images, and more! + * + * To enable SASS or LESS, add the respective loaders to module.rules + * + * https://github.com/postcss/postcss + * + * https://github.com/postcss/autoprefixer + * + * https://github.com/jonathantneal/precss + * + */`; + } +}; diff --git a/dist/creator/yeoman/utils/validate.js b/dist/creator/yeoman/utils/validate.js new file mode 100644 index 00000000000..9f8f932f7e8 --- /dev/null +++ b/dist/creator/yeoman/utils/validate.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = value => { + const pass = value.length; + if (pass) { + return true; + } + return "Please specify an answer!"; +}; diff --git a/dist/creator/yeoman/webpack-adapter.js b/dist/creator/yeoman/webpack-adapter.js new file mode 100644 index 00000000000..4a9262ea848 --- /dev/null +++ b/dist/creator/yeoman/webpack-adapter.js @@ -0,0 +1,18 @@ +"use strict"; + +const inquirer = require("inquirer"); + +/* +* @class - WebpackAdapter +* +* Custom adapter that is integrated in the scaffold. Here we can validate +* paths and answers from the user +* +*/ +module.exports = class WebpackAdapter { + prompt(questions, callback) { + const promise = inquirer.prompt(questions); + promise.then(callback || function() {}); + return promise; + } +}; diff --git a/dist/creator/yeoman/webpack-generator.js b/dist/creator/yeoman/webpack-generator.js new file mode 100644 index 00000000000..0c3ffc1ddcf --- /dev/null +++ b/dist/creator/yeoman/webpack-generator.js @@ -0,0 +1,401 @@ +"use strict"; + +const Generator = require("yeoman-generator"); +const chalk = require("chalk"); + +const createCommonsChunkPlugin = require("webpack-addons") + .createCommonsChunkPlugin; + +const Input = require("webpack-addons").Input; +const Confirm = require("webpack-addons").Confirm; +const RawList = require("webpack-addons").RawList; + +const getPackageManager = require("../../utils/package-manager") + .getPackageManager; + +const entryQuestions = require("./utils/entry"); +const getBabelPlugin = require("./utils/module"); +const getDefaultPlugins = require("./utils/plugins"); +const tooltip = require("./utils/tooltip"); + +module.exports = class WebpackGenerator extends Generator { + constructor(args, opts) { + super(args, opts); + this.isProd = false; + this.dependencies = ["webpack", "uglifyjs-webpack-plugin"]; + this.configuration = { + config: { + webpackOptions: {}, + topScope: [] + } + }; + } + prompting() { + let done = this.async(); + let self = this; + let oneOrMoreEntries; + let regExpForStyles; + let ExtractUseProps; + let outputPath = "dist"; + process.stdout.write( + `\n${chalk.bold("Insecure about some of the questions?")}\n` + ); + process.stdout.write( + `\n${chalk.bold.green( + "https://github.com/webpack/webpack-cli/blob/master/INIT.md" + )}\n\n` + ); + this.configuration.config.webpackOptions.module = { + rules: [] + }; + this.configuration.config.webpackOptions.plugins = getDefaultPlugins(); + this.configuration.config.topScope.push( + "const webpack = require('webpack')", + "const path = require('path')", + tooltip.uglify(), + "const UglifyJSPlugin = require('uglifyjs-webpack-plugin');", + "\n" + ); + this.prompt([ + Confirm("entryType", "Will your application have multiple bundles?") + ]).then(entryTypeAnswer => { + // Ask different questions for entry points + entryQuestions(self, entryTypeAnswer) + .then(entryOptions => { + this.configuration.config.webpackOptions.entry = entryOptions; + oneOrMoreEntries = Object.keys(entryOptions); + }) + .then(() => { + this.prompt([ + Input( + "outputType", + "Which folder will your generated bundles be in? [default: dist]:" + ) + ]) + .then(outputTypeAnswer => { + if (!this.configuration.config.webpackOptions.entry.length) { + this.configuration.config.topScope.push(tooltip.commonsChunk()); + this.configuration.config.webpackOptions.output = { + filename: "'[name].[chunkhash].js'", + chunkFilename: "'[name].[chunkhash].js'" + }; + } else { + this.configuration.config.webpackOptions.output = { + filename: "'[name].bundle.js'" + }; + } + if (outputTypeAnswer["outputType"].length) { + outputPath = outputTypeAnswer["outputType"]; + } + this.configuration.config.webpackOptions.output.path = `path.resolve(__dirname, '${outputPath}')`; + }) + .then(() => { + this.prompt([ + Confirm( + "prodConfirm", + "Are you going to use this in production?" + ) + ]) + .then(prodAnswer => { + if (prodAnswer["prodConfirm"] === true) { + this.isProd = true; + } else { + this.isProd = false; + } + }) + .then(() => { + this.prompt([ + Confirm("babelConfirm", "Will you be using ES2015?") + ]) + .then(ans => { + if (ans["babelConfirm"] === true) { + this.configuration.config.webpackOptions.module.rules.push( + getBabelPlugin() + ); + this.dependencies.push( + "babel-loader", + "babel-core", + "babel-preset-es2015" + ); + } + }) + .then(() => { + this.prompt([ + RawList( + "stylingType", + "Will you use one of the below CSS solutions?", + ["SASS", "LESS", "CSS", "PostCSS", "No"] + ) + ]) + .then(stylingAnswer => { + if (!this.isProd) { + ExtractUseProps = []; + } + switch (stylingAnswer["stylingType"]) { + case "SASS": + this.dependencies.push( + "sass-loader", + "node-sass", + "style-loader", + "css-loader" + ); + regExpForStyles = new RegExp(/\.(scss|css)$/); + if (this.isProd) { + ExtractUseProps = `use: [{ + loader: "css-loader", + options: { + sourceMap: true + } + }, { + loader: "sass-loader", + options: { + sourceMap: true + } + }], + fallback: "style-loader"`; + } else { + ExtractUseProps.push( + { + loader: "'style-loader'" + }, + { + loader: "'css-loader'" + }, + { + loader: "'sass-loader'" + } + ); + } + break; + case "LESS": + regExpForStyles = new RegExp(/\.(less|css)$/); + this.dependencies.push( + "less-loader", + "less", + "style-loader", + "css-loader" + ); + if (this.isProd) { + ExtractUseProps = ` + use: [{ + loader: "css-loader", + options: { + sourceMap: true + } + }, { + loader: "less-loader", + options: { + sourceMap: true + } + }], + fallback: "style-loader"`; + } else { + ExtractUseProps.push( + { + loader: "'css-loader'", + options: { + sourceMap: true + } + }, + { + loader: "'less-loader'", + options: { + sourceMap: true + } + } + ); + } + break; + case "PostCSS": + this.configuration.config.topScope.push( + tooltip.postcss(), + "const autoprefixer = require('autoprefixer');", + "const precss = require('precss');", + "\n" + ); + this.dependencies.push( + "style-loader", + "css-loader", + "postcss-loader", + "precss", + "autoprefixer" + ); + regExpForStyles = new RegExp(/\.css$/); + if (this.isProd) { + ExtractUseProps = ` + use: [{ + loader: "style-loader" + },{ + loader: "css-loader", + options: { + sourceMap: true, + importLoaders: 1 + } + }, { + loader: "postcss-loader", + options: { + plugins: function () { + return [ + precss, + autoprefixer + ]; + } + } + }], + fallback: "style-loader"`; + } else { + ExtractUseProps.push( + { + loader: "'style-loader'" + }, + { + loader: "'css-loader'", + options: { + sourceMap: true, + importLoaders: 1 + } + }, + { + loader: "'postcss-loader'", + options: { + plugins: `function () { + return [ + precss, + autoprefixer + ]; + }` + } + } + ); + } + break; + case "CSS": + this.dependencies.push( + "style-loader", + "css-loader" + ); + regExpForStyles = new RegExp(/\.css$/); + if (this.isProd) { + ExtractUseProps = `use: [{ + loader: "css-loader", + options: { + sourceMap: true + } + }], + fallback: "style-loader"`; + } else { + ExtractUseProps.push( + { + loader: "'style-loader'", + options: { + sourceMap: true + } + }, + { + loader: "'css-loader'" + } + ); + } + break; + default: + regExpForStyles = null; + } + }) + .then(() => { + // Ask if the user wants to use extractPlugin + this.prompt([ + Input( + "extractPlugin", + "If you want to bundle your CSS files, what will you name the bundle? (press enter to skip)" + ) + ]) + .then(extractAnswer => { + if (regExpForStyles) { + if (this.isProd) { + this.configuration.config.topScope.push( + tooltip.cssPlugin() + ); + this.dependencies.push( + "extract-text-webpack-plugin" + ); + if ( + extractAnswer["extractPlugin"].length !== 0 + ) { + this.configuration.config.webpackOptions.plugins.push( + "new ExtractTextPlugin('" + + extractAnswer["extractPlugin"] + + ".[contentHash].css')" + ); + } else { + this.configuration.config.webpackOptions.plugins.push( + "new ExtractTextPlugin('" + "style.css')" + ); + } + const moduleRulesObj = { + test: regExpForStyles, + use: `ExtractTextPlugin.extract({ + ${ExtractUseProps} + })` + }; + this.configuration.config.webpackOptions.module.rules.push( + moduleRulesObj + ); + this.configuration.config.topScope.push( + "const ExtractTextPlugin = require('extract-text-webpack-plugin');", + "\n" + ); + } else { + const moduleRulesObj = { + test: regExpForStyles, + use: ExtractUseProps + }; + this.configuration.config.webpackOptions.module.rules.push( + moduleRulesObj + ); + } + } + }) + .then(() => { + if ( + !this.configuration.config.webpackOptions.entry + .length + ) { + oneOrMoreEntries.forEach(prop => { + this.configuration.config.webpackOptions.plugins.push( + createCommonsChunkPlugin(prop) + ); + }); + } + done(); + }); + }); + }); + }); + }); + }); + }); + } + installPlugins() { + let asyncNamePrompt = this.async(); + let defaultName = this.isProd ? "prod" : "config"; + this.prompt([ + Input( + "nameType", + `Name your 'webpack.[name].js?' [default: '${defaultName}']:` + ) + ]) + .then(nameAnswer => { + if (nameAnswer["nameType"].length) { + this.configuration.config.configName = nameAnswer["nameType"]; + } else { + this.configuration.config.configName = defaultName; + } + }) + .then(() => { + asyncNamePrompt(); + this.runInstall(getPackageManager(), this.dependencies, { + "save-dev": true + }); + }); + } +}; diff --git a/dist/generate-loader/index.js b/dist/generate-loader/index.js new file mode 100644 index 00000000000..ff83f5f84fa --- /dev/null +++ b/dist/generate-loader/index.js @@ -0,0 +1,17 @@ +var yeoman = require("yeoman-environment"); +var LoaderGenerator = require("./loader-generator").LoaderGenerator; + +/** + * Runs a yeoman generator to create a new webpack loader project + * @returns {void} + */ +function loaderCreator() { + var env = yeoman.createEnv(); + var generatorName = "webpack-loader-generator"; + + env.registerStub(LoaderGenerator, generatorName); + + env.run(generatorName); +} + +module.exports = loaderCreator; diff --git a/dist/generate-loader/loader-generator.js b/dist/generate-loader/loader-generator.js new file mode 100644 index 00000000000..8d5720c69ab --- /dev/null +++ b/dist/generate-loader/loader-generator.js @@ -0,0 +1,58 @@ +var path = require("path"); +var _ = require("lodash"); +var webpackGenerator = require("../utils/webpack-generator"); + +/** + * Formats a string into webpack loader format + * (eg: 'style-loader', 'raw-loader') + * + * @param {string} name A loader name to be formatted + * @returns {string} The formatted string + */ +function makeLoaderName(name) { + name = _.kebabCase(name); + if (!/loader$/.test(name)) { + name += "-loader"; + } + return name; +} + +/** + * A yeoman generator class for creating a webpack + * loader project. It adds some starter loader code + * and runs `webpack-defaults`. + * + * @class LoaderGenerator + * @extends {Generator} + */ +var LoaderGenerator = webpackGenerator( + [ + { + type: "input", + name: "name", + message: "Loader name", + default: "my-loader", + filter: makeLoaderName, + validate: str => str.length > 0 + } + ], + path.join(__dirname, "templates"), + [ + "src/cjs.js.tpl", + "test/test-utils.js.tpl", + "test/unit.test.js.tpl", + "test/functional.test.js.tpl", + "test/fixtures/simple-file.js.tpl", + "examples/simple/webpack.config.js.tpl", + "examples/simple/src/index.js.tpl", + "examples/simple/src/lazy-module.js.tpl", + "examples/simple/src/static-esm-module.js.tpl" + ], + ["src/_index.js.tpl"], + gen => ({ name: gen.props.name }) +); + +module.exports = { + makeLoaderName, + LoaderGenerator +}; diff --git a/dist/generate-loader/loader-generator.test.js b/dist/generate-loader/loader-generator.test.js new file mode 100644 index 00000000000..232013598ed --- /dev/null +++ b/dist/generate-loader/loader-generator.test.js @@ -0,0 +1,15 @@ +"use strict"; + +var makeLoaderName = require("./loader-generator").makeLoaderName; + +describe("makeLoaderName", () => { + it("should kebab-case loader name and append '-loader'", () => { + var loaderName = makeLoaderName("This is a test"); + expect(loaderName).toEqual("this-is-a-test-loader"); + }); + + it("should not modify a properly formatted loader name", () => { + var loaderName = makeLoaderName("properly-named-loader"); + expect(loaderName).toEqual("properly-named-loader"); + }); +}); diff --git a/dist/generate-plugin/index.js b/dist/generate-plugin/index.js new file mode 100644 index 00000000000..82fe7dd386d --- /dev/null +++ b/dist/generate-plugin/index.js @@ -0,0 +1,17 @@ +var yeoman = require("yeoman-environment"); +var PluginGenerator = require("./plugin-generator").PluginGenerator; + +/** + * Runs a yeoman generator to create a new webpack plugin project + * @returns {void} + */ +function pluginCreator() { + var env = yeoman.createEnv(); + var generatorName = "webpack-plugin-generator"; + + env.registerStub(PluginGenerator, generatorName); + + env.run(generatorName); +} + +module.exports = pluginCreator; diff --git a/dist/generate-plugin/plugin-generator.js b/dist/generate-plugin/plugin-generator.js new file mode 100644 index 00000000000..ffc4022119a --- /dev/null +++ b/dist/generate-plugin/plugin-generator.js @@ -0,0 +1,39 @@ +var path = require("path"); +var _ = require("lodash"); +var webpackGenerator = require("../utils/webpack-generator"); + +/** + * A yeoman generator class for creating a webpack + * plugin project. It adds some starter plugin code + * and runs `webpack-defaults`. + * + * @class PluginGenerator + * @extends {Generator} + */ +var PluginGenerator = webpackGenerator( + [ + { + type: "input", + name: "name", + message: "Plugin name", + default: "my-webpack-plugin", + filter: _.kebabCase, + validate: str => str.length > 0 + } + ], + path.join(__dirname, "templates"), + [ + "src/cjs.js.tpl", + "test/test-utils.js.tpl", + "test/functional.test.js.tpl", + "examples/simple/src/index.js.tpl", + "examples/simple/src/lazy-module.js.tpl", + "examples/simple/src/static-esm-module.js.tpl" + ], + ["src/_index.js.tpl", "examples/simple/_webpack.config.js.tpl"], + gen => ({ name: _.upperFirst(_.camelCase(gen.props.name)) }) +); + +module.exports = { + PluginGenerator +}; diff --git a/lib/initialize.js b/dist/initialize.js similarity index 100% rename from lib/initialize.js rename to dist/initialize.js diff --git a/dist/migrate.js b/dist/migrate.js new file mode 100644 index 00000000000..218ce0f2c62 --- /dev/null +++ b/dist/migrate.js @@ -0,0 +1,114 @@ +"use strict"; + +const fs = require("fs"); +const chalk = require("chalk"); +const diff = require("diff"); +const inquirer = require("inquirer"); +const PLazy = require("p-lazy"); +const Listr = require("listr"); +const validateSchema = require("./utils/validateSchema.js"); +const webpackOptionsSchema = require("./utils/webpackOptionsSchema.json"); +const WebpackOptionsValidationError = require("./utils/WebpackOptionsValidationError"); + +module.exports = function transformFile( + currentConfigPath, + outputConfigPath, + options +) { + const recastOptions = Object.assign( + { + quote: "single" + }, + options + ); + const tasks = new Listr([ + { + title: "Reading webpack config", + task: ctx => + new PLazy((resolve, reject) => { + fs.readFile(currentConfigPath, "utf8", (err, content) => { + if (err) { + reject(err); + } + try { + const jscodeshift = require("jscodeshift"); + ctx.source = content; + ctx.ast = jscodeshift(content); + resolve(); + } catch (err) { + reject("Error generating AST", err); + } + }); + }) + }, + { + title: "Migrating config from v1 to v2", + task: ctx => { + const transformations = require("./transformations").transformations; + return new Listr( + Object.keys(transformations).map(key => { + const transform = transformations[key]; + return { + title: key, + task: () => transform(ctx.ast, ctx.source) + }; + }) + ); + } + } + ]); + + tasks + .run() + .then(ctx => { + const result = ctx.ast.toSource(recastOptions); + const diffOutput = diff.diffLines(ctx.source, result); + diffOutput.forEach(diffLine => { + if (diffLine.added) { + process.stdout.write(chalk.green(`+ ${diffLine.value}`)); + } else if (diffLine.removed) { + process.stdout.write(chalk.red(`- ${diffLine.value}`)); + } + }); + inquirer + .prompt([ + { + type: "confirm", + name: "confirmMigration", + message: "Are you sure these changes are fine?", + default: "Y" + } + ]) + .then(answers => { + if (answers["confirmMigration"]) { + fs.writeFile(outputConfigPath, result, "utf8", err => { + const webpackOptionsValidationErrors = validateSchema( + webpackOptionsSchema, + require(outputConfigPath) + ); + if (err) { + throw err; + } else if (webpackOptionsValidationErrors.length) { + const validationMsg = new WebpackOptionsValidationError( + webpackOptionsValidationErrors + ); + throw validationMsg.message; + } else { + console.log( + chalk.green( + `\n ✔︎ New webpack v2 config file is at ${outputConfigPath}` + ) + ); + } + }); + } else { + console.log(chalk.red("✖ Migration aborted")); + } + }); + }) + .catch(err => { + console.log(chalk.red("✖ ︎Migration aborted due to some errors")); + console.error(err); + process.exitCode = 1; + }); +}; diff --git a/dist/transformations/__testfixtures__/failing.js b/dist/transformations/__testfixtures__/failing.js new file mode 100644 index 00000000000..a52f3ffe703 --- /dev/null +++ b/dist/transformations/__testfixtures__/failing.js @@ -0,0 +1,80 @@ +var webpack = require("webpack"); +var nodeEnvironment = process.env.NODE_ENV; +var _ = require("lodash"); + +var config = { + entry: { + lib: "./app/index.js", + email: "./app/email.js" + }, + plugins: [ + new webpack.DefinePlugin({ + INCLUDE_ALL_MODULES: function includeAllModulesGlobalFn(modulesArray, application) { + modulesArray.forEach(function executeModuleIncludesFn(moduleFn) { + moduleFn(application); + }); + }, + ENVIRONMENT: JSON.stringify(nodeEnvironment) + }) + ], + output: { + path: __dirname + "/app", + filename: "bundle.js" + }, + resolve: { + root: __dirname + "/app" + }, + module: { + // preLoaders: [ + // { test: /\.js?$/, loader: 'eslint', exclude: /node_modules/ } + // ], + loaders: [ + { test: /\.js$/, exclude: /(node_modules)/, loader: "babel" }, + { test: /\.html/, exclude: [/(node_modules)/, /src\/index\.html/], loader: "html-loader" }, + { test: /\.s?css$/, loader: "style!css!sass" }, + { test: /\.(png|jpg)$/, loader: "url-loader?mimetype=image/png" } + ] + }, + // extra configuration options. + // eslint: { + // configFile: '.eslintrc.js' + // } +}; + +switch (nodeEnvironment) { + case "production": + config.plugins.push(new webpack.optimize.UglifyJsPlugin()); + case "preproduction": + config.output.path = __dirname + "/dist"; + config.plugins.push(new webpack.optimize.DedupePlugin()); + config.plugins.push(new webpack.optimize.OccurenceOrderPlugin()); + + config.output.filename = "[name].js"; + + config.entry = { + lib: ["./app/index.js", "angular", "lodash"], + email: ["./app/email.js", "angular"] + }; + + config.devtool = "source-map"; + config.output.libraryTarget = "commonjs2"; + break; + + case "test": + config.entry = "./index.js"; + break; + + case "development": + config.entry = { + lib: ["./app/index.js", "webpack/hot/dev-server"], + email: ["./app/email.js", "webpack/hot/dev-server"] + }; + config.output.filename = "[name].js"; + config.devtool = "source-map"; + break; + + default: + console.warn("Unknown or Undefined Node Environment. Please refer to package.json for available build commands."); +} + +module.exports = config; diff --git a/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-0.input.js b/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-0.input.js new file mode 100644 index 00000000000..56c89e72d84 --- /dev/null +++ b/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-0.input.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + new webpack.BannerPlugin('Banner', { raw: true, entryOnly: true }) + ] +} diff --git a/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-1.input.js b/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-1.input.js new file mode 100644 index 00000000000..0d66b9de1ac --- /dev/null +++ b/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-1.input.js @@ -0,0 +1,4 @@ +// Should do nothing if there is no banner plugin +module.exports = { + plugins: [] +} diff --git a/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-2.input.js b/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-2.input.js new file mode 100644 index 00000000000..90ecde8f0de --- /dev/null +++ b/dist/transformations/bannerPlugin/__testfixtures__/bannerPlugin-2.input.js @@ -0,0 +1,6 @@ +// Only transform if it uses the old format +module.exports = { + plugins: [ + new webpack.BannerPlugin({}) + ] +} diff --git a/dist/transformations/bannerPlugin/bannerPlugin.js b/dist/transformations/bannerPlugin/bannerPlugin.js new file mode 100644 index 00000000000..ca38235686c --- /dev/null +++ b/dist/transformations/bannerPlugin/bannerPlugin.js @@ -0,0 +1,27 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + +const utils = require("../utils"); + +module.exports = function(j , ast ) { + return utils + .findPluginsByName(j, ast, ["webpack.BannerPlugin"]) + .forEach((path ) => { + const args = path.value.arguments; // any node + // If the first argument is a literal replace it with object notation + // See https://webpack.js.org/guides/migrating/#bannerplugin-breaking-change + if (args && args.length > 1 && args[0].type === j.Literal.name) { + // and remove the first argument + path.value.arguments = [path.value.arguments[1]]; + utils.createOrUpdatePluginByName( + j, + path.parent, + "webpack.BannerPlugin", + { + banner: args[0].value + } + ); + } + }); +}; diff --git a/dist/transformations/bannerPlugin/bannerPlugin.test.js b/dist/transformations/bannerPlugin/bannerPlugin.test.js new file mode 100644 index 00000000000..004d08b6635 --- /dev/null +++ b/dist/transformations/bannerPlugin/bannerPlugin.test.js @@ -0,0 +1,7 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "bannerPlugin", "bannerPlugin-0"); +defineTest(__dirname, "bannerPlugin", "bannerPlugin-1"); +defineTest(__dirname, "bannerPlugin", "bannerPlugin-2"); diff --git a/dist/transformations/defineTest.js b/dist/transformations/defineTest.js new file mode 100644 index 00000000000..b2417c2213e --- /dev/null +++ b/dist/transformations/defineTest.js @@ -0,0 +1,95 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +/** + * Utility function to run a jscodeshift script within a unit test. + * This makes several assumptions about the environment. + * + * Notes: + * - The test should be located in a subdirectory next to the transform itself. + * Commonly tests are located in a directory called __tests__. + * + * - Test data should be located in a directory called __testfixtures__ + * alongside the transform and __tests__ directory. + * @param {String} dirName contains the name of the directory the test is located in. This + * should normally be passed via __dirname. + * @param {String} transformName contains the filename of the transform being tested, + * excluding the .js extension. + * @param {String} [testFilePrefix] Optionally contains the name of the file with the test + * data. If not specified, it defaults to the same value as `transformName`. + * This will be suffixed with ".input.js" for the input file and ".output.js" + * for the expected output. For example, if set to "foo", we will read the + * "foo.input.js" file, pass this to the transform, and expect its output to + * be equal to the contents of "foo.output.js". + * @param {Object|Boolean|String} initOptions TBD + * @return {TBD} TBD + */ +function runSingleTansform( + dirName, + transformName, + testFilePrefix, + initOptions +) { + if (!testFilePrefix) { + testFilePrefix = transformName; + } + const fixtureDir = path.join(dirName, "__testfixtures__"); + const inputPath = path.join(fixtureDir, testFilePrefix + ".input.js"); + const source = fs.readFileSync(inputPath, "utf8"); + // Assumes transform and test are on the same level + const module = require(path.join(dirName, transformName + ".js")); + // Handle ES6 modules using default export for the transform + const transform = module.default ? module.default : module; + + // Jest resets the module registry after each test, so we need to always get + // a fresh copy of jscodeshift on every test run. + let jscodeshift = require("jscodeshift/dist/core"); + if (module.parser) { + jscodeshift = jscodeshift.withParser(module.parser); + } + const ast = jscodeshift(source); + if (initOptions || typeof initOptions === "boolean") { + return transform(jscodeshift, ast, initOptions).toSource({ + quote: "single" + }); + } + return transform(jscodeshift, ast, source).toSource({ + quote: "single" + }); +} + +/** + * Handles some boilerplate around defining a simple jest/Jasmine test for a + * jscodeshift transform. + * @param {String} dirName contains the name of the directory the test is located in. This + * should normally be passed via __dirname. + * @param {String} transformName contains the filename of the transform being tested, + * excluding the .js extension. + * @param {String} [testFilePrefix] Optionally contains the name of the file with the test + * data. If not specified, it defaults to the same value as `transformName`. + * This will be suffixed with ".input.js" for the input file and ".output.js" + * for the expected output. For example, if set to "foo", we will read the + * "foo.input.js" file, pass this to the transform, and expect its output to + * be equal to the contents of "foo.output.js". + * @param {TBD} type TBD + * @return {TBD} TBD + */ +function defineTest(dirName, transformName, testFilePrefix, type) { + const testName = testFilePrefix + ? `transforms correctly using "${testFilePrefix}" data` + : "transforms correctly"; + describe(transformName, () => { + it(testName, () => { + const output = runSingleTansform( + dirName, + transformName, + testFilePrefix, + type + ); + expect(output).toMatchSnapshot(); + }); + }); +} +module.exports = defineTest; diff --git a/dist/transformations/extractTextPlugin/__testfixtures__/extractTextPlugin.input.js b/dist/transformations/extractTextPlugin/__testfixtures__/extractTextPlugin.input.js new file mode 100644 index 00000000000..f578bb4342d --- /dev/null +++ b/dist/transformations/extractTextPlugin/__testfixtures__/extractTextPlugin.input.js @@ -0,0 +1,16 @@ +let ExtractTextPlugin = require('extract-text-webpack-plugin'); +let HTMLWebpackPlugin = require('html-webpack-plugin'); + +module.export = { + module: { + rules: [ + { + test: /\.css$/, + use: ExtractTextPlugin.extract('style-loader', 'css-loader') + } + ] + }, + plugins: [ + new ExtractTextPlugin("styles.css"), + ] +} diff --git a/dist/transformations/extractTextPlugin/extractTextPlugin.js b/dist/transformations/extractTextPlugin/extractTextPlugin.js new file mode 100644 index 00000000000..05807e2ed7f --- /dev/null +++ b/dist/transformations/extractTextPlugin/extractTextPlugin.js @@ -0,0 +1,58 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + + + + + + + + +const utils = require("../utils"); + +function findInvocation( + j , + node , + pluginName +) { + let found = false; + found = + j(node) + .find(j.MemberExpression) + .filter(p => p.get("object").value.name === pluginName) + .size() > 0; + return found; +} + +module.exports = function(j , ast ) { + const changeArguments = function(p ) { + const args = p.value.arguments; + // if(args.length === 1) { + // return p; + // } else + const literalArgs = args.filter(p => + utils.isType(p, "Literal") + ); + if (literalArgs && literalArgs.length > 1) { + const newArgs = j.objectExpression( + literalArgs.map((p , index ) => + utils.createProperty(j, index === 0 ? "fallback" : "use", p.value) + ) + ); + p.value.arguments = [newArgs]; + } + return p; + }; + const name = utils.findVariableToPlugin( + j, + ast, + "extract-text-webpack-plugin" + ); + if (!name) return ast; + + return ast + .find(j.CallExpression) + .filter((p ) => findInvocation(j, p, name)) + .forEach(changeArguments); +}; diff --git a/dist/transformations/extractTextPlugin/extractTextPlugin.test.js b/dist/transformations/extractTextPlugin/extractTextPlugin.test.js new file mode 100644 index 00000000000..199067becf3 --- /dev/null +++ b/dist/transformations/extractTextPlugin/extractTextPlugin.test.js @@ -0,0 +1,5 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "extractTextPlugin"); diff --git a/dist/transformations/index.js b/dist/transformations/index.js new file mode 100644 index 00000000000..4af20b33eff --- /dev/null +++ b/dist/transformations/index.js @@ -0,0 +1,89 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + +const jscodeshift = require("jscodeshift"); +const pEachSeries = require("p-each-series"); +const PLazy = require("p-lazy"); + +const loadersTransform = require("./loaders/loaders"); +const resolveTransform = require("./resolve/resolve"); +const removeJsonLoaderTransform = require("./removeJsonLoader/removeJsonLoader"); +const uglifyJsPluginTransform = require("./uglifyJsPlugin/uglifyJsPlugin"); +const loaderOptionsPluginTransform = require("./loaderOptionsPlugin/loaderOptionsPlugin"); +const bannerPluginTransform = require("./bannerPlugin/bannerPlugin"); +const extractTextPluginTransform = require("./extractTextPlugin/extractTextPlugin"); +const removeDeprecatedPluginsTransform = require("./removeDeprecatedPlugins/removeDeprecatedPlugins"); + +const transformsObject = { + loadersTransform, + resolveTransform, + removeJsonLoaderTransform, + uglifyJsPluginTransform, + loaderOptionsPluginTransform, + bannerPluginTransform, + extractTextPluginTransform, + removeDeprecatedPluginsTransform +}; + +const transformations = Object.keys(transformsObject).reduce((res, key) => { + res[key] = (ast, source) => + transformSingleAST(ast, source, transformsObject[key]); + return res; +}, {}); + +function transformSingleAST( + ast , + source , + transformFunction +) { + return new PLazy((resolve, reject) => { + setTimeout(() => { + try { + resolve(transformFunction(jscodeshift, ast, source)); + } catch (err) { + reject(err); + } + }, 0); + }); +} + +/* + * @function transform + * + * Tranforms a given source code by applying selected transformations to the AST + * + * @param { String } source - Source file contents + * @param { Array } transformations - List of trnasformation functions in defined the + * order to apply. By default all defined transfomations. + * @param { Object } options - Reacst formatting options + * @returns { String } Transformed source code + * */ +function transform( + source , + transforms , + options +) { + const ast = jscodeshift(source); + const recastOptions = Object.assign( + { + quote: "single" + }, + options + ); + transforms = + transforms || Object.keys(transformations).map(k => transformations[k]); + return pEachSeries(transforms, f => f(ast, source)) + .then(() => { + return ast.toSource(recastOptions); + }) + .catch(err => { + console.error(err); + }); +} + +module.exports = { + transform, + transformSingleAST, + transformations +}; diff --git a/dist/transformations/index.test.js b/dist/transformations/index.test.js new file mode 100644 index 00000000000..d1a47393a81 --- /dev/null +++ b/dist/transformations/index.test.js @@ -0,0 +1,66 @@ +"use strict"; + +const transform = require("./index").transform; +const transformations = require("./index").transformations; + +const input = ` +module.exports = { + devtool: 'eval', + entry: [ + './src/index' + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'index.js' + }, + module: { + loaders: [{ + test: /.js$/, + loaders: ['babel'], + include: path.join(__dirname, 'src') + }] + }, + resolve: { + root: path.resolve('/src'), + modules: ['node_modules'] + }, + plugins: [ + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.OccurrenceOrderPlugin() + ], + debug: true +}; +`; + +describe("transform", () => { + it("should not transform if no transformations defined", done => { + transform(input, []).then(output => { + expect(output).toEqual(input); + done(); + }); + }); + + it("should transform using all transformations", done => { + transform(input).then(output => { + expect(output).toMatchSnapshot(); + done(); + }); + }); + + it("should transform only using specified transformations", done => { + transform(input, [transformations.loadersTransform]).then(output => { + expect(output).toMatchSnapshot(); + done(); + }); + }); + + it("should respect recast options", done => { + transform(input, undefined, { + quote: "double", + trailingComma: true + }).then(output => { + expect(output).toMatchSnapshot(); + done(); + }); + }); +}); diff --git a/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-0.input.js b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-0.input.js new file mode 100644 index 00000000000..e809d6a36a9 --- /dev/null +++ b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-0.input.js @@ -0,0 +1,6 @@ +// Do not create LoaderOptionsPlugin is not necessary +module.exports = { + plugins: [ + new SomePlugin() + ] +} diff --git a/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-1.input.js b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-1.input.js new file mode 100644 index 00000000000..3b8ac854d12 --- /dev/null +++ b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-1.input.js @@ -0,0 +1,9 @@ +module.exports = { + debug: true, + plugins: [ + new webpack.optimize.UglifyJsPlugin(), + new webpack.LoaderOptionsPlugin({ + foo: 'bar' + }) + ] +} diff --git a/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-2.input.js b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-2.input.js new file mode 100644 index 00000000000..eea1c515164 --- /dev/null +++ b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-2.input.js @@ -0,0 +1,9 @@ +// Don't modify LoaderOptionsPlugin +module.exports = { + plugins: [ + new SomePlugin(), + new webpack.LoaderOptionsPlugin({ + foo: 'bar' + }) + ] +} diff --git a/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-3.input.js b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-3.input.js new file mode 100644 index 00000000000..2a3baa56d23 --- /dev/null +++ b/dist/transformations/loaderOptionsPlugin/__testfixtures__/loaderOptionsPlugin-3.input.js @@ -0,0 +1,17 @@ +// Don't modify LoaderOptionsPlugin + +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +module.exports = { + entry: ['./index.js'], + output: { + filename: 'bundle.js' + }, + module: { + rules: [{ + test: /\.css$/, + use: ExtractTextPlugin.extract([ + 'css-loader' + ]) + }] + }, +} diff --git a/dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js b/dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js new file mode 100644 index 00000000000..fff3f28a4a7 --- /dev/null +++ b/dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js @@ -0,0 +1,41 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + +const isEmpty = require("lodash/isEmpty"); +const findPluginsByName = require("../utils").findPluginsByName; +const createOrUpdatePluginByName = require("../utils") + .createOrUpdatePluginByName; +const safeTraverse = require("../utils").safeTraverse; + +module.exports = function(j , ast ) { + const loaderOptions = {}; + + // If there is debug: true, set debug: true in the plugin + // TODO: remove global debug setting + // TODO: I can't figure out how to find the topmost `debug: true`. help! + if (ast.find(j.Identifier, { name: "debug" }).size()) { + loaderOptions.debug = true; + } + + // If there is UglifyJsPlugin, set minimize: true + if (findPluginsByName(j, ast, ["webpack.optimize.UglifyJsPlugin"]).size()) { + loaderOptions.minimize = true; + } + + return ast + .find(j.ArrayExpression) + .filter( + (path ) => + safeTraverse(path, ["parent", "value", "key", "name"]) === "plugins" + ) + .forEach((path ) => { + !isEmpty(loaderOptions) && + createOrUpdatePluginByName( + j, + path, + "webpack.LoaderOptionsPlugin", + loaderOptions + ); + }); +}; diff --git a/dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.test.js b/dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.test.js new file mode 100644 index 00000000000..bb3c78de07c --- /dev/null +++ b/dist/transformations/loaderOptionsPlugin/loaderOptionsPlugin.test.js @@ -0,0 +1,8 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "loaderOptionsPlugin", "loaderOptionsPlugin-0"); +defineTest(__dirname, "loaderOptionsPlugin", "loaderOptionsPlugin-1"); +defineTest(__dirname, "loaderOptionsPlugin", "loaderOptionsPlugin-2"); +defineTest(__dirname, "loaderOptionsPlugin", "loaderOptionsPlugin-3"); diff --git a/dist/transformations/loaders/__testfixtures__/loaders-0.input.js b/dist/transformations/loaders/__testfixtures__/loaders-0.input.js new file mode 100644 index 00000000000..e0d498f6506 --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-0.input.js @@ -0,0 +1,65 @@ +export default [{ + module: { + loaders: [{ + test: /\.js$/, + loader: 'babel' + }] + } +}, { + module: { + loaders: [{ + test: /\.css$/, + loader: 'style!css?modules&importLoaders=1&string=test123' + }] + } +}, { + module: { + loaders: [{ + test: /\.css$/, + loaders: [{ + loader: 'style' + }, { + loader: 'css', + query: { + modules: true + } + }] + }] + } +}, { + module: { + preLoaders:[{ + test: /\.js$/, + loader: 'eslint' + }] + } +}, { + module: { + postLoaders:[{ + test: /\.js$/, + loader: 'my-post' + }] + } +}, { + module: { + preLoaders:[{ + test: /\.js$/, + loader: 'eslint-loader' + }], + loaders: [{ + test: /\.js$/, + loader: 'babel-loader' + }] + } +}, { + module: { + loaders: [{ + test: /\.js$/, + loader: 'babel-loader' + }], + postLoaders:[{ + test: /\.js$/, + loader: 'my-post-loader' + }] + } +}]; diff --git a/dist/transformations/loaders/__testfixtures__/loaders-1.input.js b/dist/transformations/loaders/__testfixtures__/loaders-1.input.js new file mode 100644 index 00000000000..eae75024e61 --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-1.input.js @@ -0,0 +1,8 @@ +export default { + module: { + loaders: [{ + test: /\.css$/, + loader: 'style!css?modules&importLoaders=1&string=test123' + }] + } +} diff --git a/dist/transformations/loaders/__testfixtures__/loaders-2.input.js b/dist/transformations/loaders/__testfixtures__/loaders-2.input.js new file mode 100644 index 00000000000..771404a300c --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-2.input.js @@ -0,0 +1,15 @@ +export default { + module: { + loaders: [{ + test: /\.css$/, + loaders: [{ + loader: 'style' + }, { + loader: 'css', + query: { + modules: true + } + }] + }] + } +} diff --git a/dist/transformations/loaders/__testfixtures__/loaders-3.input.js b/dist/transformations/loaders/__testfixtures__/loaders-3.input.js new file mode 100644 index 00000000000..4d49e89a89b --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-3.input.js @@ -0,0 +1,8 @@ +export default { + module: { + preLoaders:[{ + test: /\.js$/, + loader: 'eslint' + }] + } +} diff --git a/dist/transformations/loaders/__testfixtures__/loaders-4.input.js b/dist/transformations/loaders/__testfixtures__/loaders-4.input.js new file mode 100644 index 00000000000..cc3e076bed9 --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-4.input.js @@ -0,0 +1,8 @@ +export default { + module: { + postLoaders:[{ + test: /\.js$/, + loader: 'my-post' + }] + } +} diff --git a/dist/transformations/loaders/__testfixtures__/loaders-5.input.js b/dist/transformations/loaders/__testfixtures__/loaders-5.input.js new file mode 100644 index 00000000000..6fd315e4d08 --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-5.input.js @@ -0,0 +1,12 @@ +export default { + module: { + preLoaders:[{ + test: /\.js$/, + loader: 'eslint-loader' + }], + loaders: [{ + test: /\.js$/, + loader: 'babel-loader' + }] + } +} diff --git a/dist/transformations/loaders/__testfixtures__/loaders-6.input.js b/dist/transformations/loaders/__testfixtures__/loaders-6.input.js new file mode 100644 index 00000000000..184e4e1ad08 --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-6.input.js @@ -0,0 +1,12 @@ +export default { + module: { + loaders: [{ + test: /\.js$/, + loader: 'babel-loader' + }], + postLoaders:[{ + test: /\.js$/, + loader: 'my-post-loader' + }] + } +} diff --git a/dist/transformations/loaders/__testfixtures__/loaders-7.input.js b/dist/transformations/loaders/__testfixtures__/loaders-7.input.js new file mode 100644 index 00000000000..f3c2915e2f8 --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-7.input.js @@ -0,0 +1,14 @@ +export default { + module: { + loaders: [ + { + test: /\.js$/, + exclude: /(node_modules)/, + loader: 'babel-loader', + query: { + presets: ['env'] + } + } + ] + } +}; diff --git a/dist/transformations/loaders/__testfixtures__/loaders-8.input.js b/dist/transformations/loaders/__testfixtures__/loaders-8.input.js new file mode 100644 index 00000000000..737e4515fca --- /dev/null +++ b/dist/transformations/loaders/__testfixtures__/loaders-8.input.js @@ -0,0 +1,14 @@ +module.exports = { + module: { + loaders: [ + { + test: /\.js$/, + loaders: [ + 'style', + 'css?modules&importLoaders=1&string=test123' + ], + include: path.join(__dirname, 'src') + } + ] + } +} diff --git a/dist/transformations/loaders/loaders.js b/dist/transformations/loaders/loaders.js new file mode 100644 index 00000000000..f8175344562 --- /dev/null +++ b/dist/transformations/loaders/loaders.js @@ -0,0 +1,314 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + + + + + + + + + +const utils = require("../utils"); + +module.exports = function(j , ast ) { + /** + * Creates an Array expression out of loaders string + * + * + * For syntaxes like + * + * { + * loader: 'style!css` + * } + * + * or + * + * { + * loaders: ['style', 'css'] + * } + * + * or + * + * loaders: [{ + * loader: 'style' + * }, + * { + * loader: 'css', + * }] + * + * it should generate + * + * { + * use: [{ + * loader: 'style' + * }, + * { + * loader: 'css' + * }] + * } + * @param {Node} path Must be an ObjectExpression + * @return {Node} [] + */ + const createArrayExpressionFromArray = function( + path + ) { + const value = path.value; + // Find paths with `loaders` keys in the given Object + const paths = value.properties.filter((prop ) => + prop.key.name.startsWith("loader") + ); + // For each pair of key and value + paths.forEach((pair ) => { + // Replace 'loaders' Identifier with 'use' + pair.key.name = "use"; + // If the value is an Array + if (pair.value.type === j.ArrayExpression.name) { + // replace its elements + const pairValue = pair.value; + pair.value = j.arrayExpression( + pairValue.elements.map(arrElement => { + // If items of the array are Strings + if (arrElement.type === j.Literal.name) { + // Replace with `{ loader: LOADER }` Object + return j.objectExpression([ + utils.createProperty(j, "loader", arrElement.value) + ]); + } + // otherwise keep the existing element + return arrElement; + }) + ); + // If the value is String of loaders like 'style!css' + } else if (pair.value.type === j.Literal.name) { + // Replace it with Array expression of loaders + const literalValue = pair.value; + pair.value = j.arrayExpression( + literalValue.value.split("!").map(loader => { + return j.objectExpression([ + utils.createProperty(j, "loader", loader) + ]); + }) + ); + } + }); + return path; + }; + + const createLoaderWithQuery = ( + p + ) => { + let properties = p.value.properties; + let loaderValue = properties.reduce( + (val, prop) => (prop.key.name === "loader" ? prop.value.value : val), + "" + ); + let loader = loaderValue.split("?")[0]; + let query = loaderValue.split("?")[1]; + let options = query.split("&").map(option => { + const param = option.split("="); + const key = param[0]; + const val = param[1] || true; // No value in query string means it is truthy value + return j.objectProperty(j.identifier(key), utils.createLiteral(j, val)); + }); + let loaderProp = utils.createProperty(j, "loader", loader); + let queryProp = j.property( + "init", + j.identifier("options"), + j.objectExpression(options) + ); + return j.objectExpression([loaderProp, queryProp]); + }; + + const findLoaderWithQueryString = (p ) => { + return p.value.properties.reduce((predicate, prop) => { + return ( + (utils.safeTraverse(prop, ["value", "value", "indexOf"]) && + prop.value.value.indexOf("?") > -1) || + predicate + ); + }, false); + }; + + /** + * If the path value is `loaders` and it's located in `module` object + * we assume it's the loader's section + * + * @param {Node} path [description] + * @return {Node} [description] + */ + const checkForLoader = path => + path.value.name === "loaders" && + utils.safeTraverse(path, [ + "parent", + "parent", + "parent", + "node", + "key", + "name" + ]) === "module"; + + /** + * Puts node path that is pre- or postLoader into `enforce` key + * + * @param {Node} p [] + * @returns {*} [] + */ + const fitIntoLoaders = ( + p + ) => { + let loaders; + p.value.properties.map(prop => { + const keyName = prop.key.name; + if (keyName === "loaders") { + loaders = prop.value; + } + }); + p.value.properties.map(prop => { + const keyName = prop.key.name; + if (keyName !== "loaders") { + const enforceVal = keyName === "preLoaders" ? "pre" : "post"; + prop.value.elements.map(elem => { + elem.properties.push(utils.createProperty(j, "enforce", enforceVal)); + if (loaders && loaders.type === "ArrayExpression") { + loaders.elements.push(elem); + } else { + prop.key.name = "loaders"; + } + }); + } + }); + if (loaders) { + p.value.properties = p.value.properties.filter( + prop => prop.key.name === "loaders" + ); + } + return p; + }; + + /** + * Find pre and postLoaders + * + * @param {[type]} IPath [description] + * @return {[type]} [description] + */ + const prepostLoaders = () => + ast + .find(j.ObjectExpression) + .filter(p => utils.findObjWithOneOfKeys(p, ["preLoaders", "postLoaders"])) + .forEach(fitIntoLoaders); + + /** + * Convert top level `loaders` to `rules` + * See https://webpack.js.org/configuration/module/#module-rules + * @param {[type]} IPath [description] + * @return {[type]} [description] + */ + const loadersToRules = () => + ast + .find(j.Identifier) + .filter(checkForLoader) + .forEach((p ) => (p.value.name = "rules")); + + /** + * Converts 'loader' and 'loaders' to Array of {Rule.Use} + * + * @returns {Node} [] + */ + const loadersToArrayExpression = () => + ast + .find(j.ObjectExpression) + .filter(path => utils.findObjWithOneOfKeys(path, ["loader", "loaders"])) + .filter( + path => + utils.safeTraverse(path, [ + "parent", + "parent", + "node", + "key", + "name" + ]) === "rules" + ) + .forEach(createArrayExpressionFromArray); + + /** + * Finds loaders with options encoded as query string and replaces it with options obejct + * + * i.e. for loader like + * + * { + * loader: 'css?modules&importLoaders=1&string=test123' + * } + * + * it should generate + * { + * loader: 'css-loader', + * options: { + * modules: true, + * importLoaders: 1, + * string: 'test123' + * } + * } + * + * @type {[type]} + * @returns {Node} [] + */ + const loaderWithQueryParam = () => + ast + .find(j.ObjectExpression) + .filter(p => utils.findObjWithOneOfKeys(p, ["loader"])) + .filter(findLoaderWithQueryString) + .replaceWith(createLoaderWithQuery); + + /** + * Finds nodes with `query` key and replaces it with `options` + * + * i.e. for + * { + * query: { ... } + * } + * + * it should generate + * + * { + * options: { ... } + * } + * @returns {Node} [] + */ + const loaderWithQueryProp = () => + ast + .find(j.Identifier) + .filter(p => p.value.name === "query") + .replaceWith(j.identifier("options")); + + /** + * Adds required `-loader` suffix to loader with missing suffix + * i.e. for `babel` it should generate `babel-loader` + * @returns {Node} [] + */ + const addLoaderSuffix = () => + ast.find(j.ObjectExpression).forEach(path => { + path.value.properties.forEach(prop => { + if ( + prop.key.name === "loader" && + utils.safeTraverse(prop, ["value", "value"]) && + !prop.value.value.endsWith("-loader") + ) { + prop.value = j.literal(prop.value.value + "-loader"); + } + }); + }); + + const transforms = [ + prepostLoaders, + loadersToRules, + loadersToArrayExpression, + loaderWithQueryParam, + loaderWithQueryProp, + addLoaderSuffix + ]; + transforms.forEach(t => t()); + + return ast; +}; diff --git a/dist/transformations/loaders/loaders.test.js b/dist/transformations/loaders/loaders.test.js new file mode 100644 index 00000000000..90d36acda2a --- /dev/null +++ b/dist/transformations/loaders/loaders.test.js @@ -0,0 +1,13 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "loaders", "loaders-0"); +defineTest(__dirname, "loaders", "loaders-1"); +defineTest(__dirname, "loaders", "loaders-2"); +defineTest(__dirname, "loaders", "loaders-3"); +defineTest(__dirname, "loaders", "loaders-4"); +defineTest(__dirname, "loaders", "loaders-5"); +defineTest(__dirname, "loaders", "loaders-6"); +defineTest(__dirname, "loaders", "loaders-7"); +defineTest(__dirname, "loaders", "loaders-8"); diff --git a/dist/transformations/outputPath/__testfixtures__/outputPath-0.input.js b/dist/transformations/outputPath/__testfixtures__/outputPath-0.input.js new file mode 100644 index 00000000000..085268fadbe --- /dev/null +++ b/dist/transformations/outputPath/__testfixtures__/outputPath-0.input.js @@ -0,0 +1,5 @@ +module.exports = { + output: { + path: 'dist' + } +} diff --git a/dist/transformations/outputPath/__testfixtures__/outputPath-1.input.js b/dist/transformations/outputPath/__testfixtures__/outputPath-1.input.js new file mode 100644 index 00000000000..c7a0ed58e7a --- /dev/null +++ b/dist/transformations/outputPath/__testfixtures__/outputPath-1.input.js @@ -0,0 +1,6 @@ +const path = require('path'); +module.exports = { + output: { + path: path.join(__dirname, 'dist') + } +} diff --git a/dist/transformations/outputPath/__testfixtures__/outputPath-2.input.js b/dist/transformations/outputPath/__testfixtures__/outputPath-2.input.js new file mode 100644 index 00000000000..de8436ae6c3 --- /dev/null +++ b/dist/transformations/outputPath/__testfixtures__/outputPath-2.input.js @@ -0,0 +1,6 @@ +const p = require('path'); +module.exports = { + output: { + path: 'dist' + } +} diff --git a/dist/transformations/outputPath/outputPath.js b/dist/transformations/outputPath/outputPath.js new file mode 100644 index 00000000000..0f08c6694c4 --- /dev/null +++ b/dist/transformations/outputPath/outputPath.js @@ -0,0 +1,73 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + +const utils = require("../utils"); + +module.exports = function(j , ast ) { + const literalOutputPath = ast + .find(j.ObjectExpression) + .filter( + (p ) => + utils.safeTraverse(p, ["parentPath", "value", "key", "name"]) === + "output" + ) + .find(j.Property) + .filter( + (p ) => + utils.safeTraverse(p, ["value", "key", "name"]) === "path" && + utils.safeTraverse(p, ["value", "value", "type"]) === "Literal" + ); + + if (literalOutputPath) { + let pathVarName = "path"; + let isPathPresent = false; + const pathDecalaration = ast + .find(j.VariableDeclarator) + .filter( + (p ) => + utils.safeTraverse(p, ["value", "init", "callee", "name"]) === + "require" + ) + .filter( + (p ) => + utils.safeTraverse(p, ["value", "init", "arguments"]) && + p.value.init.arguments.reduce((isPresent, a) => { + return (a.type === "Literal" && a.value === "path") || isPresent; + }, false) + ); + + if (pathDecalaration) { + isPathPresent = true; + pathDecalaration.forEach(p => { + pathVarName = utils.safeTraverse(p, ["value", "id", "name"]); + }); + } + const finalPathName = (pathVarName ); + literalOutputPath + .find(j.Literal) + .replaceWith((p ) => replaceWithPath(j, p, finalPathName)); + + if (!isPathPresent) { + const pathRequire = (utils.getRequire(j, "path", "path") ); + return ast + .find(j.Program) + .replaceWith(p => + j.program([].concat(pathRequire).concat(p.value.body)) + ); + } + } + return ast; +}; + +function replaceWithPath( + j , + p , + pathVarName +) { + const convertedPath = j.callExpression( + j.memberExpression(j.identifier(pathVarName), j.identifier("join"), false), + [j.identifier("__dirname"), p.value] + ); + return convertedPath; +} diff --git a/dist/transformations/outputPath/outputPath.test.js b/dist/transformations/outputPath/outputPath.test.js new file mode 100644 index 00000000000..1312905c074 --- /dev/null +++ b/dist/transformations/outputPath/outputPath.test.js @@ -0,0 +1,7 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "outputPath", "outputPath-0"); +defineTest(__dirname, "outputPath", "outputPath-1"); +defineTest(__dirname, "outputPath", "outputPath-2"); diff --git a/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-0.input.js b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-0.input.js new file mode 100644 index 00000000000..133c4984bfd --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-0.input.js @@ -0,0 +1,6 @@ +// Works for OccurrenceOrderPlugin +module.exports = { + plugins: [ + new webpack.optimize.OccurrenceOrderPlugin(), + ] +} diff --git a/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-1.input.js b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-1.input.js new file mode 100644 index 00000000000..a64dab79b37 --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-1.input.js @@ -0,0 +1,6 @@ +// Works for DedupePlugin +module.exports = { + plugins: [ + new webpack.optimize.DedupePlugin(), + ] +} diff --git a/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-2.input.js b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-2.input.js new file mode 100644 index 00000000000..26150117db4 --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-2.input.js @@ -0,0 +1,8 @@ +// Doesn't remove unmatched plugins +module.exports = { + plugins: [ + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.DedupePlugin() + ] +} diff --git a/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-3.input.js b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-3.input.js new file mode 100644 index 00000000000..1d5194460e3 --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-3.input.js @@ -0,0 +1,7 @@ +// This should throw +export default (config) => { + config.plugins.push(new webpack.optimize.UglifyJsPlugin()); + config.plugins.push(new webpack.optimize.DedupePlugin()); + config.plugins.push(new webpack.optimize.OccurrenceOrderPlugin()); + return config +} diff --git a/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-4.input.js b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-4.input.js new file mode 100644 index 00000000000..fab47caf971 --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/__testfixtures__/removeDeprecatedPlugins-4.input.js @@ -0,0 +1,8 @@ +// This should throw +const inst = new webpack.optimize.OccurrenceOrderPlugin() +export default (config) => { + config.plugins = [ + inst + ] + return config +} diff --git a/dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js b/dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js new file mode 100644 index 00000000000..97d101bfa44 --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js @@ -0,0 +1,59 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + +const codeFrame = require("babel-code-frame"); +const chalk = require("chalk"); +const utils = require("../utils"); + +const example = `plugins: [ + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin(), + new webpack.optimize.DedupePlugin() +]`; + +module.exports = function(j , ast , source ) { + // List of deprecated plugins to remove + // each item refers to webpack.optimize.[NAME] construct + const deprecatedPlugingsList = [ + "webpack.optimize.OccurrenceOrderPlugin", + "webpack.optimize.DedupePlugin" + ]; + + return utils + .findPluginsByName(j, ast, deprecatedPlugingsList) + .forEach((path ) => { + // For now we only support the case there plugins are defined in an Array + const arrayPath = utils.safeTraverse(path, [ + "parent", + "value" + ]); + if (arrayPath && utils.isType(arrayPath, "ArrayExpression")) { + // Check how many plugins are defined and + // if there is only last plugin left remove `plugins: []` node + const arrayElementsPath = utils.safeTraverse(arrayPath, ["elements"]); + if (arrayElementsPath && arrayElementsPath.length === 1) { + j(path.parent.parent).remove(); + } else { + j(path).remove(); + } + } else { + const startLoc = path.value.loc.start; + console.log(` +${chalk.red( + "Only plugins instantiated in the array can be automatically removed i.e.:" + )} + +${codeFrame(example, null, null, { highlightCode: true })} + +${chalk.red("but you use it like this:")} + +${codeFrame(source, startLoc.line, startLoc.column, { highlightCode: true })} + +${chalk.red("Please remove deprecated plugins manually. ")} +See ${chalk.underline( + "https://webpack.js.org/guides/migrating/" + )} for more information.`); + } + }); +}; diff --git a/dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js b/dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js new file mode 100644 index 00000000000..e029dff487d --- /dev/null +++ b/dist/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.test.js @@ -0,0 +1,9 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-0"); +defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-1"); +defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-2"); +defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-3"); +defineTest(__dirname, "removeDeprecatedPlugins", "removeDeprecatedPlugins-4"); diff --git a/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-0.input.js b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-0.input.js new file mode 100644 index 00000000000..48517948142 --- /dev/null +++ b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-0.input.js @@ -0,0 +1,19 @@ +export default { + module: { + rules: [{ + test: /\.yml/, + use: [ + { + loader: 'json-loader' + }, + { + loader: 'another-loader' + }, + { + loader: 'yml-loader' + } + ] + }] + } +} + diff --git a/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-1.input.js b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-1.input.js new file mode 100644 index 00000000000..1040b5ff919 --- /dev/null +++ b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-1.input.js @@ -0,0 +1,12 @@ +export default { + module: { + rules: [{ + test: /\.yml/, + use: [{ + loader: 'json-loader' + }, { + loader: 'yml-loader' + }] + }] + } +} diff --git a/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-2.input.js b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-2.input.js new file mode 100644 index 00000000000..39b473705d0 --- /dev/null +++ b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-2.input.js @@ -0,0 +1,10 @@ +export default { + module: { + rules: [ + { + test: /\.json/, + loader: 'json-loader' + } + ] + } +} diff --git a/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-3.input.js b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-3.input.js new file mode 100644 index 00000000000..be47fd92ee2 --- /dev/null +++ b/dist/transformations/removeJsonLoader/__testfixtures__/removeJsonLoader-3.input.js @@ -0,0 +1,11 @@ +export default { + module: { + rules: [{ + test: /\.json/, + use: [{ + loader: 'json-loader' + }] + }] + } +} + diff --git a/dist/transformations/removeJsonLoader/removeJsonLoader.js b/dist/transformations/removeJsonLoader/removeJsonLoader.js new file mode 100644 index 00000000000..0867bae9fa5 --- /dev/null +++ b/dist/transformations/removeJsonLoader/removeJsonLoader.js @@ -0,0 +1,60 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + + + + + + +const utils = require("../utils"); + +module.exports = function(j , ast ) { + function removeLoaderByName(path , name ) { + const loadersNode = path.value.value; + switch (loadersNode.type) { + case j.ArrayExpression.name: { + let loaders = (loadersNode ).elements.map(p => { + return utils.safeTraverse(p, ["properties", "0", "value", "value"]); + }); + const loaderIndex = loaders.indexOf(name); + if (loaders.length && loaderIndex > -1) { + // Remove loader from the array + loaders.splice(loaderIndex, 1); + // and from AST + loadersNode.elements.splice(loaderIndex, 1); + } + + // If there are no loaders left, remove the whole Rule object + if (loaders.length === 0) { + j(path.parent).remove(); + } + break; + } + case j.Literal.name: { + // If only the loader with the matching name was used + // we can remove the whole Property node completely + if ((loadersNode ).value === name) { + j(path.parent).remove(); + } + break; + } + } + } + + function removeLoaders(ast ) { + ast + .find(j.Property, { key: { name: "use" } }) + .forEach((path ) => removeLoaderByName(path, "json-loader")); + + ast + .find(j.Property, { key: { name: "loader" } }) + .forEach((path ) => removeLoaderByName(path, "json-loader")); + } + + const transforms = [removeLoaders]; + + transforms.forEach(t => t(ast)); + + return ast; +}; diff --git a/dist/transformations/removeJsonLoader/removeJsonLoader.test.js b/dist/transformations/removeJsonLoader/removeJsonLoader.test.js new file mode 100644 index 00000000000..a448e68bf02 --- /dev/null +++ b/dist/transformations/removeJsonLoader/removeJsonLoader.test.js @@ -0,0 +1,8 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "removeJsonLoader", "removeJsonLoader-0"); +defineTest(__dirname, "removeJsonLoader", "removeJsonLoader-1"); +defineTest(__dirname, "removeJsonLoader", "removeJsonLoader-2"); +defineTest(__dirname, "removeJsonLoader", "removeJsonLoader-3"); diff --git a/dist/transformations/resolve/__testfixtures__/resolve.input.js b/dist/transformations/resolve/__testfixtures__/resolve.input.js new file mode 100644 index 00000000000..2b83fcf26ce --- /dev/null +++ b/dist/transformations/resolve/__testfixtures__/resolve.input.js @@ -0,0 +1,20 @@ +import path from 'path'; + +export default [{ + resolve: { + root: path.resolve('/src') + } +}, { + resolve: { + root: [path.resolve('/src')] + } +}, { + resolve: { + root: [path.resolve('/src'), 'node_modules'] + } +}, { + resolve: { + root: path.resolve('/src'), + modules: ['node_modules'] + } +}]; diff --git a/dist/transformations/resolve/resolve.js b/dist/transformations/resolve/resolve.js new file mode 100644 index 00000000000..797671e83ec --- /dev/null +++ b/dist/transformations/resolve/resolve.js @@ -0,0 +1,68 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + + + + + + + +module.exports = function transformer(j , ast ) { + const getRootVal = ( + p + ) => { + return p.node.value.properties.filter(prop => prop.key.name === "root")[0]; + }; + + const getRootIndex = p => { + return p.node.value.properties.reduce((rootIndex, prop, index) => { + return prop.key.name === "root" ? index : rootIndex; + }, -1); + }; + + const isModulePresent = (p ) => { + const modules = p.node.value.properties.filter( + prop => prop.key.name === "modules" + ); + return modules.length > 0 && modules[0]; + }; + + const createModuleArray = ( + p + ) => { + const rootVal = getRootVal(p); + let modulesVal = null; + if (rootVal.value.type === "ArrayExpression") { + modulesVal = rootVal.value.elements; + } else { + modulesVal = [rootVal.value]; + } + let module = isModulePresent(p); + + if (!module) { + module = j.property( + "init", + j.identifier("modules"), + j.arrayExpression(modulesVal) + ); + p.node.value.properties = p.node.value.properties.concat([module]); + } else { + module.value.elements = module.value.elements.concat(modulesVal); + } + const rootIndex = getRootIndex(p); + p.node.value.properties.splice(rootIndex, 1); + return p; + }; + + return ast + .find(j.Property) + .filter((p ) => { + return ( + p.node.key.name === "resolve" && + p.node.value.properties.filter(prop => prop.key.name === "root") + .length === 1 + ); + }) + .forEach(createModuleArray); +}; diff --git a/dist/transformations/resolve/resolve.test.js b/dist/transformations/resolve/resolve.test.js new file mode 100644 index 00000000000..9cbd9d22957 --- /dev/null +++ b/dist/transformations/resolve/resolve.test.js @@ -0,0 +1,5 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "resolve"); diff --git a/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js b/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js new file mode 100644 index 00000000000..900f7042075 --- /dev/null +++ b/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-0.input.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + new webpack.optimize.UglifyJsPlugin() + ] +} diff --git a/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js b/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js new file mode 100644 index 00000000000..57d7eb1c192 --- /dev/null +++ b/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-1.input.js @@ -0,0 +1,6 @@ +module.exports = { + devtool: "source-map", + plugins: [ + new webpack.optimize.UglifyJsPlugin({}) + ] +} diff --git a/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js b/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js new file mode 100644 index 00000000000..3c13f02b203 --- /dev/null +++ b/dist/transformations/uglifyJsPlugin/__testfixtures__/uglifyJsPlugin-2.input.js @@ -0,0 +1,8 @@ +module.exports = { + devtool: "cheap-source-map", + plugins: [ + new webpack.optimize.UglifyJsPlugin({ + compress: {} + }) + ] +} diff --git a/dist/transformations/uglifyJsPlugin/uglifyJsPlugin.js b/dist/transformations/uglifyJsPlugin/uglifyJsPlugin.js new file mode 100644 index 00000000000..cadddba29f8 --- /dev/null +++ b/dist/transformations/uglifyJsPlugin/uglifyJsPlugin.js @@ -0,0 +1,28 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + +const findPluginsByName = require("../utils").findPluginsByName; + +module.exports = function(j , ast ) { + function createSourceMapsProperty() { + return j.property("init", j.identifier("sourceMap"), j.identifier("true")); + } + + return findPluginsByName(j, ast, [ + "webpack.optimize.UglifyJsPlugin" + ]).forEach(path => { + const args = path.value.arguments; + + if (args.length) { + // Plugin is called with object as arguments + j(path) + .find(j.ObjectExpression) + .get("properties") + .value.push(createSourceMapsProperty()); + } else { + // Plugin is called without arguments + args.push(j.objectExpression([createSourceMapsProperty()])); + } + }); +}; diff --git a/dist/transformations/uglifyJsPlugin/uglifyJsPlugin.test.js b/dist/transformations/uglifyJsPlugin/uglifyJsPlugin.test.js new file mode 100644 index 00000000000..4b43262cbdc --- /dev/null +++ b/dist/transformations/uglifyJsPlugin/uglifyJsPlugin.test.js @@ -0,0 +1,7 @@ +"use strict"; + +const defineTest = require("../defineTest"); + +defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-0"); +defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-1"); +defineTest(__dirname, "uglifyJsPlugin", "uglifyJsPlugin-2"); diff --git a/dist/transformations/utils.js b/dist/transformations/utils.js new file mode 100644 index 00000000000..61115bba5ae --- /dev/null +++ b/dist/transformations/utils.js @@ -0,0 +1,630 @@ +// +// eslint-disable-next-line node/no-unsupported-features + + + + + + + + + + + +function safeTraverse(obj , paths ) { + let val = obj; + let idx = 0; + + while (idx < paths.length) { + if (!val) { + return null; + } + val = val[paths[idx]]; + idx++; + } + return val; +} + +// Convert nested MemberExpressions to strings like webpack.optimize.DedupePlugin +function memberExpressionToPathString(path ) { + if (path && path.object) { + return [memberExpressionToPathString(path.object), path.property.name].join( + "." + ); + } + return path.name; +} + +// Convert Array like ['webpack', 'optimize', 'DedupePlugin'] to nested MemberExpressions +function pathsToMemberExpression(j , paths ) { + if (!paths.length) { + return null; + } else if (paths.length === 1) { + return j.identifier(paths[0]); + } else { + const first = paths.slice(0, 1); + const rest = paths.slice(1); + return j.memberExpression( + pathsToMemberExpression(j, rest), + pathsToMemberExpression(j, first) + ); + } +} + +/* +* @function findPluginsByName +* +* Find paths that match `new name.space.PluginName()` for a given array of plugin names +* +* @param j — jscodeshift API +* @param { Node } node - Node to start search from +* @param { Array } pluginNamesArray - Array of plugin names like `webpack.LoaderOptionsPlugin` +* @returns Path + * */ +function findPluginsByName( + j , + node , + pluginNamesArray +) { + return node.find(j.NewExpression).filter(path => { + return pluginNamesArray.some( + plugin => + memberExpressionToPathString(path.get("callee").value) === plugin + ); + }); +} + +/* + * @function findPluginsRootNodes + * + * Finds the path to the `plugins: []` node + * + * @param j — jscodeshift API + * @param { Node } node - Node to start search from + * @returns Path + * */ +function findPluginsRootNodes( + j , + node +) { + return node.find(j.Property, { key: { name: "plugins" } }); +} + +/* + * @function createProperty + * + * Creates an Object's property with a given key and value + * + * @param j — jscodeshift API + * @param { string | number } key - Property key + * @param { string | number | boolean } value - Property value + * @returns Node + * */ +function createProperty( + j , + key , + value +) { + return j.property( + "init", + createIdentifierOrLiteral(j, key), + createLiteral(j, value) + ); +} + +/* + * @function createLiteral + * + * Creates an appropriate literal property + * + * @param j — jscodeshift API + * @param { string | boolean | number } val + * @returns { Node } + * */ + +function createLiteral( + j , + val +) { + let literalVal = val; + // We'll need String to native type conversions + if (typeof val === "string") { + // 'true' => true + if (val === "true") literalVal = true; + // 'false' => false + if (val === "false") literalVal = false; + // '1' => 1 + if (!isNaN(Number(val))) literalVal = Number(val); + } + return j.literal(literalVal); +} + +/* + * @function createIdentifierOrLiteral + * + * Creates an appropriate identifier or literal property + * + * @param j — jscodeshift API + * @param { string | boolean | number } val + * @returns { Node } + * */ + +function createIdentifierOrLiteral(j , val ) { + // IPath | IPath doesn't work, find another way + let literalVal = val; + // We'll need String to native type conversions + if (typeof val === "string" || val.__paths) { + // 'true' => true + if (val === "true") { + literalVal = true; + return j.literal(literalVal); + } + // 'false' => false + if (val === "false") { + literalVal = false; + return j.literal(literalVal); + } + // '1' => 1 + if (!isNaN(Number(val))) { + literalVal = Number(val); + return j.literal(literalVal); + } + + if (val.__paths) { + return createExternalRegExp(j, val); + } else { + // Use identifier instead + // TODO: Check if literalVal is a valid Identifier! + return j.identifier(literalVal); + } + } + return j.literal(literalVal); +} +/* + * @function createOrUpdatePluginByName + * + * Findes or creates a node for a given plugin name string with options object + * If plugin decalaration already exist, options are merged. + * + * @param j — jscodeshift API + * @param { NodePath } rooNodePath - `plugins: []` NodePath where plugin should be added. See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths + * @param { string } pluginName - ex. `webpack.LoaderOptionsPlugin` + * @param { Object } options - plugin options + * @returns void + * */ +function createOrUpdatePluginByName( + j , + rootNodePath , + pluginName , + options +) { + const pluginInstancePath = findPluginsByName(j, j(rootNodePath), [ + pluginName + ]); + let optionsProps; + if (options) { + optionsProps = Object.keys(options).map(key => { + return createProperty(j, key, options[key]); + }); + } + + // If plugin declaration already exist + if (pluginInstancePath.size()) { + pluginInstancePath.forEach(path => { + // There are options we want to pass as argument + if (optionsProps) { + const args = path.value.arguments; + if (args.length) { + // Plugin is called with object as arguments + // we will merge those objects + let currentProps = j(path) + .find(j.ObjectExpression) + .get("properties"); + + optionsProps.forEach((opt ) => { + // Search for same keys in the existing object + const existingProps = j(currentProps) + .find(j.Identifier) + .filter(path => opt.key.value === path.value.name); + + if (existingProps.size()) { + // Replacing values for the same key + existingProps.forEach(path => { + j(path.parent).replaceWith(opt); + }); + } else { + // Adding new key:values + currentProps.value.push(opt); + } + }); + } else { + // Plugin is called without arguments + args.push(j.objectExpression(optionsProps)); + } + } + }); + } else { + let argumentsArray = []; + if (optionsProps) { + argumentsArray = [j.objectExpression(optionsProps)]; + } + const loaderPluginInstance = j.newExpression( + pathsToMemberExpression(j, pluginName.split(".").reverse()), + argumentsArray + ); + rootNodePath.value.elements.push(loaderPluginInstance); + } +} + +/* + * @function findVariableToPlugin + * + * Finds the variable to which a third party plugin is assigned to + * + * @param j — jscodeshift API + * @param { Node } rootNode - `plugins: []` Root Node. See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths + * @param { string } pluginPackageName - ex. `extract-text-plugin` + * @returns { string } variable name - ex. 'var s = require(s) gives "s"` + * */ + +function findVariableToPlugin( + j , + rootNode , + pluginPackageName +) { + const moduleVarNames = rootNode + .find(j.VariableDeclarator) + .filter(j.filters.VariableDeclarator.requiresModule(pluginPackageName)) + .nodes(); + if (moduleVarNames.length === 0) return null; + return moduleVarNames.pop().id.name; +} + +/* +* @function isType +* +* Returns true if type is given type +* @param { Node} path - pathNode +* @param { string } type - node type +* @returns {boolean} +*/ + +function isType(path , type ) { + return path.type === type; +} + +function findObjWithOneOfKeys(p , keyNames ) { + return p.value.properties.reduce((predicate, prop) => { + const name = prop.key.name; + return keyNames.indexOf(name) > -1 || predicate; + }, false); +} + +/* +* @function getRequire +* +* Returns constructed require symbol +* @param j — jscodeshift API +* @param { string } constName - Name of require +* @param { string } packagePath - path of required package +* @returns {NodePath} - the created ast +*/ + +function getRequire( + j , + constName , + packagePath +) { + return j.variableDeclaration("const", [ + j.variableDeclarator( + j.identifier(constName), + j.callExpression(j.identifier("require"), [j.literal(packagePath)]) + ) + ]); +} + +/* +* @function checkIfExistsAndAddValue +* +* If a prop exists, it overrides it, else it creates a new one +* @param j — jscodeshift API +* @param { Node } node - objectexpression to check +* @param { string } key - Key of the property +* @param { string } value - computed value of the property +* @returns - nothing +*/ + +function checkIfExistsAndAddValue( + j , + node , + key , + value +) { + const existingProp = node.value.properties.filter( + prop => prop.key.name === key + ); + let prop; + if (existingProp.length > 0) { + prop = existingProp[0]; + prop.value = value; + } else { + prop = j.property("init", j.identifier(key), value); + node.value.properties.push(prop); + } +} + +/* +* @function createEmptyArrayProperty +* +* Creates an empty array +* @param j — jscodeshift API +* @param { String } key - st name +* @returns - { Array } arr - An empty array +*/ +function createEmptyArrayProperty( + j , + key +) { + return j.property("init", j.identifier(key), j.arrayExpression([])); +} + +/* +* @function createArrayWithChildren +* +* Creates an array and iterates on an object with properties +* @param j — jscodeshift API +* @param { String } key - object name +* @param { string } subProps - computed value of the property +* @param { Boolean } shouldDropKeys - +* @returns - { Array } arr - An array with the object properties +*/ + +function createArrayWithChildren( + j , + key , + subProps , + shouldDropKeys +) { + let arr = createEmptyArrayProperty(j, key); + if (shouldDropKeys) { + subProps.forEach((subProperty ) => { + let objectOfArray = j.objectExpression([]); + if (typeof subProperty !== "string") { + loopThroughObjects(j, objectOfArray, subProperty); + arr.value.elements.push(objectOfArray); + } else { + return arr.value.elements.push( + createIdentifierOrLiteral(j, subProperty) + ); + } + }); + } else { + Object.keys(subProps[key]).forEach(subProperty => { + arr.value.elements.push( + createIdentifierOrLiteral(j, subProps[key][subProperty]) + ); + }); + } + return arr; +} + +/* +* @function loopThroughObjects +* +* Loops through an object and adds property to an object with no identifier +* @param j — jscodeshift API +* @param { Node } p - node to add value to +* @param { Object } obj - Object to loop through +* @returns - { Function|Node } - Either pushes the node, or reruns until +* nothing is left +*/ + +function loopThroughObjects( + j , + p , + obj +) { + Object.keys(obj).forEach(prop => { + if (prop.indexOf("inject") >= 0) { + // TODO to insert the type of node, identifier or literal + const propertyExpression = createIdentifierOrLiteral( + j, + obj[prop] + ); + return p.properties.push(propertyExpression); + } + // eslint-disable-next-line no-irregular-whitespace + if (typeof obj[prop] !== "object" || obj[prop] instanceof RegExp) { + p.properties.push( + createObjectWithSuppliedProperty( + j, + prop, + createIdentifierOrLiteral(j, obj[prop]) + ) + ); + } else if (Array.isArray(obj[prop])) { + let arrayProp = createArrayWithChildren(j, prop, obj[prop], true); + p.properties.push(arrayProp); + } else { + let objectBlock = j.objectExpression([]); + let propertyOfBlock = createObjectWithSuppliedProperty( + j, + prop, + objectBlock + ); + loopThroughObjects(j, objectBlock, obj[prop]); + p.properties.push(propertyOfBlock); + } + }); +} + +/* +* @function createObjectWithSuppliedProperty +* +* Creates an object with an supplied property as parameter +* @param j — jscodeshift API +* @param { String } key - object name +* @param { Node } prop - property to be added +* @returns - { Node } - An property with the supplied property +*/ + +function createObjectWithSuppliedProperty( + j , + key , + prop +) { + return j.property("init", j.identifier(key), prop); +} + +/* +* @function createExternalRegExp +* +* Finds a regexp property with an already parsed AST from the user +* @param j — jscodeshift API +* @param { String } prop - property to find the value at +* @returns - { Node } - A literal node with the found regexp +*/ + +function createExternalRegExp(j , prop ) { + let regExpProp = prop.__paths[0].value.program.body[0].expression; + return j.literal(regExpProp.value); +} + +/* +* @function pushCreateProperty +* +* Creates a property and pushes the value to a node +* @param j — jscodeshift API +* @param { Node } p - Node to push against +* @param { String } key - key used as identifier +* @param { String } val - property value +* @returns - { Node } - Returns node the pushed property +*/ + +function pushCreateProperty( + j , + p , + key , + val +) { + let property ; + if (val.hasOwnProperty("type")) { + property = val; + } else { + property = createIdentifierOrLiteral(j, val); + } + return p.value.properties.push( + createObjectWithSuppliedProperty(j, key, property) + ); +} + +/* +* @function pushObjectKeys +* +* @param j — jscodeshift API +* @param { Node } p - path to push +* @param { Object } webpackProperties - The object to loop over +* @param { String } name - Key that will be the identifier we find and add values to +* @returns - { Node/Function } Returns a function that will push a node if +*subProperty is an array, else it will invoke a function that will push a single node +*/ + +function pushObjectKeys( + j , + p , + webpackProperties , + name +) { + p.value.properties + .filter(n => safeTraverse(n, ["key", "name"]) === name) + .forEach(prop => { + Object.keys(webpackProperties).forEach(webpackProp => { + if (webpackProp.indexOf("inject") >= 0) { + return prop.value.properties.push( + createIdentifierOrLiteral(j, webpackProperties[webpackProp]) + ); + } else if (Array.isArray(webpackProperties[webpackProp])) { + const propArray = createArrayWithChildren( + j, + webpackProp, + webpackProperties[webpackProp], + true + ); + return prop.value.properties.push(propArray); + } else if ( + typeof webpackProperties[webpackProp] !== "object" || + webpackProperties[webpackProp].__paths || + webpackProperties[webpackProp] instanceof RegExp + ) { + return pushCreateProperty( + j, + prop, + webpackProp, + webpackProperties[webpackProp] + ); + } else { + pushCreateProperty(j, prop, webpackProp, j.objectExpression([])); + return pushObjectKeys( + j, + prop, + webpackProperties[webpackProp], + webpackProp + ); + } + }); + }); +} + +/* +* @function isAssignment +* +* Checks if we are at the correct node and later invokes a callback +* for the transforms to either use their own transform function or +* use pushCreateProperty if the transform doesn't expect any properties +* @param j — jscodeshift API +* @param { Node } p - Node to push against +* @param { Function } cb - callback to be invoked +* @param { String } identifier - key to use as property +* @param { Object } property - WebpackOptions that later will be converted via +* pushCreateProperty via WebpackOptions[identifier] +* @returns - { Function } cb - Returns the callback and pushes a new node +*/ + +function isAssignment( + j , + p , + cb , + identifier , + property +) { + if (p.parent.value.type === "AssignmentExpression") { + if (j) { + return cb(j, p, identifier, property); + } else { + return cb(p); + } + } +} + +module.exports = { + safeTraverse, + createProperty, + findPluginsByName, + findPluginsRootNodes, + createOrUpdatePluginByName, + findVariableToPlugin, + isType, + createLiteral, + createIdentifierOrLiteral, + findObjWithOneOfKeys, + getRequire, + checkIfExistsAndAddValue, + createArrayWithChildren, + createEmptyArrayProperty, + createObjectWithSuppliedProperty, + createExternalRegExp, + pushCreateProperty, + pushObjectKeys, + isAssignment, + loopThroughObjects +}; diff --git a/dist/transformations/utils.test.js b/dist/transformations/utils.test.js new file mode 100644 index 00000000000..a70091a40e2 --- /dev/null +++ b/dist/transformations/utils.test.js @@ -0,0 +1,358 @@ +"use strict"; + +const j = require("jscodeshift/dist/core"); +const utils = require("./utils"); + +describe("utils", () => { + describe("createProperty", () => { + it("should create properties for Boolean", () => { + const res = utils.createProperty(j, "foo", true); + expect(j(j.objectExpression([res])).toSource()).toMatchSnapshot(); + }); + + it("should create properties for Number", () => { + const res = utils.createProperty(j, "foo", -1); + expect(j(j.objectExpression([res])).toSource()).toMatchSnapshot(); + }); + + it("should create properties for String", () => { + const res = utils.createProperty(j, "foo", "bar"); + expect(j(j.objectExpression([res])).toSource()).toMatchSnapshot(); + }); + + xit("should create properties for complex keys", () => { + const res = utils.createProperty(j, "foo-bar", "bar"); + expect(j(j.objectExpression([res])).toSource()).toMatchSnapshot(); + }); + + it("should create properties for non-literal keys", () => { + const res = utils.createProperty(j, 1, "bar"); + expect(j(j.objectExpression([res])).toSource()).toMatchSnapshot(); + }); + }); + + describe("findPluginsByName", () => { + it("should find plugins in AST", () => { + const ast = j(` +{ foo: new webpack.optimize.UglifyJsPlugin() } +`); + const res = utils.findPluginsByName(j, ast, [ + "webpack.optimize.UglifyJsPlugin" + ]); + expect(res.size()).toEqual(1); + }); + + it("should find all plugins in AST", () => { + const ast = j(` +[ + new UglifyJsPlugin(), + new TestPlugin() +] +`); + const res = utils.findPluginsByName(j, ast, [ + "UglifyJsPlugin", + "TestPlugin" + ]); + expect(res.size()).toEqual(2); + }); + + it("should not find false positives", () => { + const ast = j(` +{ foo: new UglifyJsPlugin() } +`); + const res = utils.findPluginsByName(j, ast, [ + "webpack.optimize.UglifyJsPlugin" + ]); + expect(res.size()).toEqual(0); + }); + }); + + describe("findPluginsRootNodes", () => { + it("should find plugins: [] nodes", () => { + const ast = j(` +var a = { plugins: [], foo: { plugins: [] } } +`); + const res = utils.findPluginsRootNodes(j, ast); + expect(res.size()).toEqual(2); + }); + + it("should not find plugins: [] nodes", () => { + const ast = j(` +var a = { plugs: [] } +`); + const res = utils.findPluginsRootNodes(j, ast); + expect(res.size()).toEqual(0); + }); + }); + + describe("createOrUpdatePluginByName", () => { + it("should create a new plugin without arguments", () => { + const ast = j("{ plugins: [] }"); + ast.find(j.ArrayExpression).forEach(node => { + utils.createOrUpdatePluginByName(j, node, "Plugin"); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + + it("should create a new plugin with arguments", () => { + const ast = j("{ plugins: [] }"); + ast.find(j.ArrayExpression).forEach(node => { + utils.createOrUpdatePluginByName(j, node, "Plugin", { + foo: "bar" + }); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + + it("should add an object as an argument", () => { + const ast = j("[new Plugin()]"); + ast.find(j.ArrayExpression).forEach(node => { + utils.createOrUpdatePluginByName(j, node, "Plugin", { + foo: true + }); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + + it("should merge options objects", () => { + const ast = j("[new Plugin({ foo: true })]"); + ast.find(j.ArrayExpression).forEach(node => { + utils.createOrUpdatePluginByName(j, node, "Plugin", { + bar: "baz", + foo: false + }); + utils.createOrUpdatePluginByName(j, node, "Plugin", { + "baz-long": true + }); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + + describe("findVariableToPlugin", () => { + it("should find the variable name of a plugin", () => { + const ast = j(` + var packageName = require('package-name'); + var someOtherVar = somethingElse; + var otherPackage = require('other-package'); + `); + const foundVar = utils.findVariableToPlugin(j, ast, "other-package"); + expect(foundVar).toEqual("otherPackage"); + }); + }); + + describe("createLiteral", () => { + it("should create basic literal", () => { + const literal = utils.createLiteral(j, "stringLiteral"); + expect(j(literal).toSource()).toMatchSnapshot(); + }); + it("should create boolean", () => { + const literal = utils.createLiteral(j, "true"); + expect(j(literal).toSource()).toMatchSnapshot(); + }); + }); + + describe("createIdentifierOrLiteral", () => { + it("should create basic literal", () => { + const literal = utils.createIdentifierOrLiteral(j, "'stringLiteral'"); + expect(j(literal).toSource()).toMatchSnapshot(); + }); + it("should create boolean", () => { + const literal = utils.createIdentifierOrLiteral(j, "true"); + expect(j(literal).toSource()).toMatchSnapshot(); + }); + }); + + describe("findObjWithOneOfKeys", () => { + it("should find keys", () => { + const ast = j(` + var ab = { + a: 1, + b: 2 + } + `); + expect( + ast + .find(j.ObjectExpression) + .filter(p => utils.findObjWithOneOfKeys(p, ["a"])) + .size() + ).toEqual(1); + }); + }); + + describe("getRequire", () => { + it("should create a require statement", () => { + const require = utils.getRequire(j, "filesys", "fs"); + expect(j(require).toSource()).toMatchSnapshot(); + }); + }); + + describe("checkIfExistsAndAddValue", () => { + it("should create new prop if none exist", () => { + const ast = j(` + module.exports = { + entry: 'index.js' + } + `); + ast + .find(j.ObjectExpression) + .forEach(node => + utils.checkIfExistsAndAddValue( + j, + node, + "externals", + j.literal("React") + ) + ); + expect(ast.toSource()).toMatchSnapshot(); + }); + it("should override prop if it exists", () => { + const ast = j(` + module.exports = { + entry: 'index.js' + } + `); + ast + .find(j.ObjectExpression) + .forEach(node => + utils.checkIfExistsAndAddValue(j, node, "entry", j.literal("app.js")) + ); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("createArrayWithChildren", () => { + it("should find an prop that matches key and create an array with it", () => { + const ast = j("{}"); + let key = "react"; + let reactArr = { + react: ["'bo'"] + }; + const arr = utils.createArrayWithChildren(j, key, reactArr, false); + ast.find(j.Program).forEach(node => j(node).replaceWith(arr)); + expect(ast.toSource()).toMatchSnapshot(); + }); + it("should add all children of an array to a new one with a supplied key", () => { + const ast = j("{}"); + let key = "myVeryOwnKey"; + let helloWorldArray = ["'hello'", "world"]; + const arr = utils.createArrayWithChildren(j, key, helloWorldArray, true); + ast.find(j.Program).forEach(node => j(node).replaceWith(arr)); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("createEmptyArrayProperty", () => { + it("should create an array with no properties", () => { + const ast = j("{}"); + const arr = utils.createEmptyArrayProperty(j, "its-lit"); + ast.find(j.Program).forEach(node => j(node).replaceWith(arr)); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + + describe("createObjectWithSuppliedProperty", () => { + it("should create an object with a property supplied by us", () => { + const ast = j("{}"); + const prop = utils.createObjectWithSuppliedProperty( + j, + "its-lit", + j.objectExpression([]) + ); + ast.find(j.Program).forEach(node => j(node).replaceWith(prop)); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("createExternalRegExp", () => { + it("should create an regExp property that has been parsed by jscodeshift", () => { + const ast = j("{}"); + const reg = j("'\t'"); + const prop = utils.createExternalRegExp(j, reg); + ast.find(j.Program).forEach(node => j(node).replaceWith(prop)); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("pushCreateProperty", () => { + it("should create an object or property and push the value to a node", () => { + const ast = j(`module.exports = { + pushMe: {} + }`); + ast + .find(j.Identifier) + .filter(n => n.value.name === "pushMe") + .forEach(node => { + const heavyNodeNoSafeTraverse = node.parentPath.value; + utils.pushCreateProperty( + j, + heavyNodeNoSafeTraverse, + "just", + "pushed" + ); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("pushObjectKeys", () => { + it("should push object to an node using Object.keys", () => { + const ast = j(`module.exports = { + pushMe: {} + }`); + const webpackProperties = { + hello: { + world: { + its: "'great'" + } + } + }; + ast.find(j.ObjectExpression).forEach(node => { + utils.pushObjectKeys(j, node, webpackProperties, "pushMe"); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("loopThroughObjects", () => { + it("Use recursion and add elements to an node", () => { + const ast = j("module.exports = {}"); + const webpackProperties = { + hello: { + webpack: "cli" + } + }; + ast.find(j.ObjectExpression).forEach(node => { + return utils.loopThroughObjects(j, node.value, webpackProperties); + }); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); + describe("isAssignment", () => { + it("should invoke a callback if parent type is AssignmentExpression", () => { + const ast = j("module.exports = {}"); + const myObj = "Heyho"; + const myKey = "context"; + + ast + .find(j.ObjectExpression) + .filter(n => + utils.isAssignment(j, n, utils.pushCreateProperty, myKey, myObj) + ); + expect(ast.toSource()).toMatchSnapshot(); + }); + it("should allow custom transform functions instead of singularProperty", () => { + const ast = j("module.exports = {}"); + + function createPluginsProperty(p) { + const webpackProperties = { + plugins: ["one", "two", "three"] + }; + const pluginArray = utils.createArrayWithChildren( + j, + "plugins", + webpackProperties + ); + return p.value.properties.push(pluginArray); + } + ast + .find(j.ObjectExpression) + .filter(n => utils.isAssignment(null, n, createPluginsProperty)); + expect(ast.toSource()).toMatchSnapshot(); + }); + }); +}); diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 00000000000..9d9d3eb9681 --- /dev/null +++ b/dist/types.js @@ -0,0 +1,192 @@ +// +/* eslint-disable node/no-unsupported-features */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/** + * [IAstNodeCommentLine description] + * @type {Object} + */ + + + + + + + + + +/** + * [ILoc description] + * @type {Object} + */ + + + + + + + + + + + +/** + * [ITokenType description] + * @type {Object} + */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/utils/WebpackOptionsValidationError.js b/dist/utils/WebpackOptionsValidationError.js new file mode 100644 index 00000000000..5af67962802 --- /dev/null +++ b/dist/utils/WebpackOptionsValidationError.js @@ -0,0 +1,249 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Gajus Kuizinas @gajus +*/ +"use strict"; + +const webpackOptionsSchema = require("./webpackOptionsSchema.json"); + +const getSchemaPart = (path, parents, additionalPath) => { + parents = parents || 0; + path = path.split("/"); + path = path.slice(0, path.length - parents); + if (additionalPath) { + additionalPath = additionalPath.split("/"); + path = path.concat(additionalPath); + } + let schemaPart = webpackOptionsSchema; + for (let i = 1; i < path.length; i++) { + const inner = schemaPart[path[i]]; + if (inner) schemaPart = inner; + } + return schemaPart; +}; + +const getSchemaPartText = (schemaPart, additionalPath) => { + if (additionalPath) { + for (let i = 0; i < additionalPath.length; i++) { + const inner = schemaPart[additionalPath[i]]; + if (inner) schemaPart = inner; + } + } + while (schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref); + let schemaText = WebpackOptionsValidationError.formatSchema(schemaPart); + if (schemaPart.description) schemaText += `\n${schemaPart.description}`; + return schemaText; +}; + +const indent = (str, prefix, firstLine) => { + if (firstLine) { + return prefix + str.replace(/\n(?!$)/g, "\n" + prefix); + } else { + return str.replace(/\n(?!$)/g, `\n${prefix}`); + } +}; + +class WebpackOptionsValidationError extends Error { + constructor(validationErrors) { + super(); + if (Error.hasOwnProperty("captureStackTrace")) { + Error.captureStackTrace(this, this.constructor); + } + this.name = "WebpackOptionsValidationError"; + + this.message = + "Invalid configuration object. " + + "Webpack has been initialised using a configuration object that does not match the API schema.\n" + + validationErrors + .map( + err => + " - " + + indent( + WebpackOptionsValidationError.formatValidationError(err), + " ", + false + ) + ) + .join("\n"); + this.validationErrors = validationErrors; + } + + static formatSchema(schema, prevSchemas) { + prevSchemas = prevSchemas || []; + + const formatInnerSchema = (innerSchema, addSelf) => { + if (!addSelf) + return WebpackOptionsValidationError.formatSchema( + innerSchema, + prevSchemas + ); + if (prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)"; + return WebpackOptionsValidationError.formatSchema( + innerSchema, + prevSchemas.concat(schema) + ); + }; + + if (schema.type === "string") { + if (schema.minLength === 1) return "non-empty string"; + else if (schema.minLength > 1) + return `string (min length ${schema.minLength})`; + return "string"; + } else if (schema.type === "boolean") { + return "boolean"; + } else if (schema.type === "number") { + return "number"; + } else if (schema.type === "object") { + if (schema.properties) { + const required = schema.required || []; + return `object { ${Object.keys(schema.properties) + .map(property => { + if (required.indexOf(property) < 0) return property + "?"; + return property; + }) + .concat(schema.additionalProperties ? ["..."] : []) + .join(", ")} }`; + } + if (schema.additionalProperties) { + return `object { : ${formatInnerSchema( + schema.additionalProperties + )} }`; + } + return "object"; + } else if (schema.type === "array") { + return `[${formatInnerSchema(schema.items)}]`; + } + + switch (schema.instanceof) { + case "Function": + return "function"; + case "RegExp": + return "RegExp"; + } + if (schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true); + if (schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & "); + if (schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | "); + if (schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | "); + if (schema.enum) + return schema.enum.map(item => JSON.stringify(item)).join(" | "); + return JSON.stringify(schema, 0, 2); + } + + static formatValidationError(err) { + const dataPath = `configuration${err.dataPath}`; + if (err.keyword === "additionalProperties") { + const baseMessage = `${dataPath} has an unknown property '${err.params + .additionalProperty}'. These properties are valid:\n${getSchemaPartText( + err.parentSchema + )}`; + if (!err.dataPath) { + switch (err.params.additionalProperty) { + case "debug": + return ( + `${baseMessage}\n` + + "The 'debug' property was removed in webpack 2.\n" + + "Loaders should be updated to allow passing this option via loader options in module.rules.\n" + + "Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode:\n" + + "plugins: [\n" + + " new webpack.LoaderOptionsPlugin({\n" + + " debug: true\n" + + " })\n" + + "]" + ); + } + return ( + baseMessage + + "\n" + + "For typos: please correct them.\n" + + "For loader options: webpack 2 no longer allows custom properties in configuration.\n" + + " Loaders should be updated to allow passing options via loader options in module.rules.\n" + + " Until loaders are updated one can use the LoaderOptionsPlugin to pass these options to the loader:\n" + + " plugins: [\n" + + " new webpack.LoaderOptionsPlugin({\n" + + " // test: /\\.xxx$/, // may apply this only for some modules\n" + + " options: {\n" + + ` ${err.params.additionalProperty}: ...\n` + + " }\n" + + " })\n" + + " ]" + ); + } + return baseMessage; + } else if (err.keyword === "oneOf" || err.keyword === "anyOf") { + if (err.children && err.children.length > 0) { + return ( + `${dataPath} should be one of these:\n${getSchemaPartText( + err.parentSchema + )}\n` + + `Details:\n${err.children + .map( + err => + " * " + + indent( + WebpackOptionsValidationError.formatValidationError(err), + " ", + false + ) + ) + .join("\n")}` + ); + } + return `${dataPath} should be one of these:\n${getSchemaPartText( + err.parentSchema + )}`; + } else if (err.keyword === "enum") { + if ( + err.parentSchema && + err.parentSchema.enum && + err.parentSchema.enum.length === 1 + ) { + return `${dataPath} should be ${getSchemaPartText(err.parentSchema)}`; + } + return `${dataPath} should be one of these:\n${getSchemaPartText( + err.parentSchema + )}`; + } else if (err.keyword === "allOf") { + return `${dataPath} should be:\n${getSchemaPartText(err.parentSchema)}`; + } else if (err.keyword === "type") { + switch (err.params.type) { + case "object": + return `${dataPath} should be an object.`; + case "string": + return `${dataPath} should be a string.`; + case "boolean": + return `${dataPath} should be a boolean.`; + case "number": + return `${dataPath} should be a number.`; + case "array": + return `${dataPath} should be an array:\n${getSchemaPartText( + err.parentSchema + )}`; + } + return `${dataPath} should be ${err.params.type}:\n${getSchemaPartText( + err.parentSchema + )}`; + } else if (err.keyword === "instanceof") { + return `${dataPath} should be an instance of ${getSchemaPartText( + err.parentSchema + )}.`; + } else if (err.keyword === "required") { + const missingProperty = err.params.missingProperty.replace(/^\./, ""); + return `${dataPath} misses the property '${missingProperty}'.\n${getSchemaPartText( + err.parentSchema, + ["properties", missingProperty] + )}`; + } else if (err.keyword === "minLength" || err.keyword === "minItems") { + if (err.params.limit === 1) return `${dataPath} should not be empty.`; + else return `${dataPath} ${err.message}`; + } else { + // eslint-disable-line no-fallthrough + return `${dataPath} ${err.message} (${JSON.stringify( + err, + 0, + 2 + )}).\n${getSchemaPartText(err.parentSchema)}`; + } + } +} + +module.exports = WebpackOptionsValidationError; diff --git a/dist/utils/copy-utils.js b/dist/utils/copy-utils.js new file mode 100644 index 00000000000..d161d38943d --- /dev/null +++ b/dist/utils/copy-utils.js @@ -0,0 +1,57 @@ +var path = require("path"); + +/** + * Takes in a file path in the `./templates` directory. Copies that + * file to the destination, with the `.tpl` extension stripped. + * + * @param {Generator} generator A Yeoman Generator instance + * @param {string} templateDir Absolute path to template directory + * @returns {Function} A curried function that takes a file path and copies it + */ +var generatorCopy = ( + generator, + templateDir +) => /** @param {string} filePath */ filePath => { + var sourceParts = templateDir.split(path.delimiter); + sourceParts.push.apply(sourceParts, filePath.split("/")); + var targetParts = path.dirname(filePath).split("/"); + targetParts.push(path.basename(filePath, ".tpl")); + + generator.fs.copy( + path.join.apply(null, sourceParts), + generator.destinationPath(path.join.apply(null, targetParts)) + ); +}; + +/** + * Takes in a file path in the `./templates` directory. Copies that + * file to the destination, with the `.tpl` extension and `_` prefix + * stripped. Passes `this.props` to the template. + * + * @param {Generator} generator A Yeoman Generator instance + * @param {string} templateDir Absolute path to template directory + * @param {any} templateData An object containing the data passed to + * the template files. + * @returns {Function} A curried function that takes a file path and copies it + */ +var generatorCopyTpl = ( + generator, + templateDir, + templateData +) => /** @param {string} filePath */ filePath => { + var sourceParts = templateDir.split(path.delimiter); + sourceParts.push.apply(sourceParts, filePath.split("/")); + var targetParts = path.dirname(filePath).split("/"); + targetParts.push(path.basename(filePath, ".tpl").slice(1)); + + generator.fs.copyTpl( + path.join.apply(null, sourceParts), + generator.destinationPath(path.join.apply(null, targetParts)), + templateData + ); +}; + +module.exports = { + generatorCopy, + generatorCopyTpl +}; diff --git a/dist/utils/npm-exists.js b/dist/utils/npm-exists.js new file mode 100644 index 00000000000..f8a11b8d9d4 --- /dev/null +++ b/dist/utils/npm-exists.js @@ -0,0 +1,34 @@ +"use strict"; + +const got = require("got"); +const chalk = require("chalk"); +const constant = value => () => value; + +/* +* @function npmExists +* +* Checks if the given dependency/module is registered on npm +* +* @param { String } moduleName - The dependency to be checked +* @returns { } constant - Returns either true or false, +* based on if it exists or not +*/ + +module.exports = function npmExists(moduleName) { + //eslint-disable-next-line + if (moduleName.length <= 14 || moduleName.slice(0, 14) !== "webpack-addons") { + throw new TypeError( + chalk.bold(`${moduleName} isn't a valid name.\n`) + + chalk.red( + "\nIt should be prefixed with 'webpack-addons', but have different suffix.\n" + ) + ); + } + const hostname = "https://www.npmjs.org"; + const pkgUrl = `${hostname}/package/${moduleName}`; + return got(pkgUrl, { + method: "HEAD" + }) + .then(constant(true)) + .catch(constant(false)); +}; diff --git a/dist/utils/npm-exists.spec.js b/dist/utils/npm-exists.spec.js new file mode 100644 index 00000000000..1ee26346139 --- /dev/null +++ b/dist/utils/npm-exists.spec.js @@ -0,0 +1,16 @@ +"use strict"; +const exists = require("./npm-exists"); + +describe("npm-exists", () => { + it("should sucessfully existence of a published module", () => { + exists("webpack-addons-ylvis").then(status => { + expect(status).toBe(true); + }); + }); + + it("should return false for the existence of a fake module", () => { + exists("webpack-addons-noop").then(status => { + expect(status).toBe(false); + }); + }); +}); diff --git a/dist/utils/npm-packages-exists.js b/dist/utils/npm-packages-exists.js new file mode 100644 index 00000000000..d8999771023 --- /dev/null +++ b/dist/utils/npm-packages-exists.js @@ -0,0 +1,38 @@ +"use strict"; + +const npmExists = require("./npm-exists"); +const resolvePackages = require("./resolve-packages"); + +/* +* @function npmPackagesExists +* +* Loops through an array and checks if a package is registered +* on npm and throws an error if it is not. +* +* @param { Array } pkg - Array of packages to check existence of +* @returns { Array } resolvePackages - Returns an process to install the packages +*/ + +module.exports = function npmPackagesExists(pkg) { + let acceptedPackages = []; + pkg.forEach(addon => { + npmExists(addon) + .then(moduleExists => { + if (!moduleExists) { + Error.stackTraceLimit = 0; + throw new TypeError("Package isn't registered on npm."); + } + if (moduleExists) { + acceptedPackages.push(addon); + } + }) + .catch(err => { + console.error(err.stack || err); + process.exit(0); + }) + .then(() => { + if (acceptedPackages.length === pkg.length) + return resolvePackages(acceptedPackages); + }); + }); +}; diff --git a/dist/utils/package-manager.js b/dist/utils/package-manager.js new file mode 100644 index 00000000000..57e335dfb8b --- /dev/null +++ b/dist/utils/package-manager.js @@ -0,0 +1,61 @@ +"use strict"; + +const path = require("path"); +const fs = require("fs"); +const spawn = require("cross-spawn"); +const globalPath = require("global-modules"); + +const SPAWN_FUNCTIONS = { + npm: spawnNPM, + yarn: spawnYarn +}; + +function spawnNPM(pkg, isNew) { + return spawn.sync("npm", [isNew ? "install" : "update", "-g", pkg], { + stdio: "inherit" + }); +} + +function spawnYarn(pkg, isNew) { + return spawn.sync("yarn", ["global", isNew ? "add" : "upgrade", pkg], { + stdio: "inherit" + }); +} +/* +* @function spawnChild +* +* Spawns a new process that installs the addon/dependency +* +* @param { String } pkg - The dependency to be installed +* @returns { } spawn - Installs the package +*/ + +function spawnChild(pkg) { + const pkgPath = path.resolve(globalPath, pkg); + const packageManager = getPackageManager(); + const isNew = !fs.existsSync(pkgPath); + + return SPAWN_FUNCTIONS[packageManager](pkg, isNew); +} + +/* +* @function getPackageManager +* +* Returns the name of package manager to use, +* preferring yarn over npm if available +* +* @returns { String } - The package manager name +*/ + +function getPackageManager() { + if (spawn.sync("yarn", [" --version"], { stdio: "ignore" }).error) { + return "npm"; + } + + return "yarn"; +} + +module.exports = { + getPackageManager, + spawnChild +}; diff --git a/dist/utils/package-manager.spec.js b/dist/utils/package-manager.spec.js new file mode 100644 index 00000000000..e45b920fb4a --- /dev/null +++ b/dist/utils/package-manager.spec.js @@ -0,0 +1,90 @@ +"use strict"; + +jest.mock("cross-spawn"); +jest.mock("fs"); + +describe("package-manager", () => { + const packageManager = require("./package-manager"); + const spawn = require("cross-spawn"); + const fs = require("fs"); + + const defaultSyncResult = { + pid: 1234, + output: [null, null, null], + stdout: null, + stderr: null, + signal: null, + status: 1, + error: null + }; + + function mockSpawnErrorOnce() { + spawn.sync.mockReturnValueOnce( + Object.assign({}, defaultSyncResult, { + status: null, + error: new Error() + }) + ); + } + + spawn.sync.mockReturnValue(defaultSyncResult); + + it("should return 'yarn' from getPackageManager if it's installed", () => { + expect(packageManager.getPackageManager()).toEqual("yarn"); + }); + + it("should return 'npm' from getPackageManager if yarn is not installed", () => { + mockSpawnErrorOnce(); + expect(packageManager.getPackageManager()).toEqual("npm"); + }); + + it("should spawn yarn add from spawnChild", () => { + const packageName = "some-pkg"; + + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "yarn", + ["global", "add", packageName], + { stdio: "inherit" } + ); + }); + + it("should spawn yarn upgrade from spawnChild", () => { + const packageName = "some-pkg"; + + fs.existsSync.mockReturnValueOnce(true); + + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "yarn", + ["global", "upgrade", packageName], + { stdio: "inherit" } + ); + }); + + it("should spawn npm install from spawnChild", () => { + const packageName = "some-pkg"; + + mockSpawnErrorOnce(); + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "npm", + ["install", "-g", packageName], + { stdio: "inherit" } + ); + }); + + it("should spawn npm update from spawnChild", () => { + const packageName = "some-pkg"; + + mockSpawnErrorOnce(); + fs.existsSync.mockReturnValueOnce(true); + + packageManager.spawnChild(packageName); + expect(spawn.sync).toHaveBeenLastCalledWith( + "npm", + ["update", "-g", packageName], + { stdio: "inherit" } + ); + }); +}); diff --git a/dist/utils/resolve-packages.js b/dist/utils/resolve-packages.js new file mode 100644 index 00000000000..00f88bf35ed --- /dev/null +++ b/dist/utils/resolve-packages.js @@ -0,0 +1,70 @@ +"use strict"; + +const path = require("path"); +const chalk = require("chalk"); +const globalPath = require("global-modules"); + +const creator = require("../creator/index").creator; + +const spawnChild = require("./package-manager").spawnChild; + +/* +* @function processPromise +* +* Attaches a promise to the installation of the package +* +* @param { Function } child - The function to attach a promise to +* @returns { } promise - Returns a promise to the installation +*/ + +function processPromise(child) { + return new Promise(function(resolve, reject) { + //eslint-disable-line + if (child.status !== 0) { + reject(); + } else { + resolve(); + } + }); +} + +/* +* @function resolvePackages +* +* Resolves and installs the packages, later sending them to @creator +* +* @param { Array } pkg - The dependencies to be installed +* @returns { } creator - Builds +* a webpack configuration through yeoman or throws an error +*/ + +module.exports = function resolvePackages(pkg) { + Error.stackTraceLimit = 30; + + let packageLocations = []; + + pkg.forEach(addon => { + processPromise(spawnChild(addon)) + .then(() => { + try { + packageLocations.push(path.resolve(globalPath, addon)); + } catch (err) { + console.log("Package wasn't validated correctly.."); + console.log("Submit an issue for", pkg, "if this persists"); + console.log("\nReason: \n"); + console.error(chalk.bold.red(err)); + process.exitCode = 1; + } + }) + .catch(err => { + console.log("Package Coudln't be installed, aborting.."); + console.log("\nReason: \n"); + console.error(chalk.bold.red(err)); + process.exitCode = 1; + }) + .then(() => { + if (packageLocations.length === pkg.length) + return creator(packageLocations); + }); + }); +}; diff --git a/dist/utils/resolve-packages.spec.js b/dist/utils/resolve-packages.spec.js new file mode 100644 index 00000000000..6b5b1392770 --- /dev/null +++ b/dist/utils/resolve-packages.spec.js @@ -0,0 +1,34 @@ +"use strict"; + +const getLoc = require("../../__mocks__/inquirer/resolve.mock"); + +describe("resolve-packages", () => { + let moduleLoc; + + afterEach(() => { + moduleLoc = null; + }); + + it("should resolve a location of a published module", () => { + moduleLoc = getLoc(["webpack-addons-ylvis"]); + expect(moduleLoc).toEqual(["../../node_modules/webpack-addons-ylvis"]); + }); + + it("should be empty if argument is blank", () => { + // normally caught before getting resolved + moduleLoc = getLoc([" "]); + expect(moduleLoc).toEqual(["../../node_modules/ "]); + }); + + it("should resolve multiple locations of published modules", () => { + /* we're testing multiple paths here. At Github this up for discussion, because if + * we validate each package on each run, we can catch and build the questions in init gradually + * while we get one filepath at the time. If not, this is a workaround. + */ + moduleLoc = getLoc(["webpack-addons-ylvis", "webpack-addons-noop"]); + expect(moduleLoc).toEqual([ + "../../node_modules/webpack-addons-ylvis", + "../../node_modules/webpack-addons-noop" + ]); + }); +}); diff --git a/dist/utils/validateSchema.js b/dist/utils/validateSchema.js new file mode 100644 index 00000000000..3a20782e68f --- /dev/null +++ b/dist/utils/validateSchema.js @@ -0,0 +1,67 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Gajus Kuizinas @gajus +*/ +"use strict"; + +/* eslint-disable */ +const Ajv = require("ajv"); +const ajv = new Ajv({ + errorDataPath: "configuration", + allErrors: true, + verbose: true +}); +require("ajv-keywords")(ajv, ["instanceof"]); +/* eslint-enable */ + +function validateSchema(schema, options) { + if (Array.isArray(options)) { + const errors = options.map(options => validateObject(schema, options)); + errors.forEach((list, idx) => { + list.forEach(function applyPrefix(err) { + err.dataPath = `[${idx}]${err.dataPath}`; + if (err.children) { + err.children.forEach(applyPrefix); + } + }); + }); + return errors.reduce((arr, items) => { + return arr.concat(items); + }, []); + } else { + return validateObject(schema, options); + } +} + +function validateObject(schema, options) { + const validate = ajv.compile(schema); + const valid = validate(options); + return valid ? [] : filterErrors(validate.errors); +} + +function filterErrors(errors) { + let newErrors = []; + errors.forEach(err => { + const dataPath = err.dataPath; + let children = []; + newErrors = newErrors.filter(oldError => { + if (oldError.dataPath.includes(dataPath)) { + if (oldError.children) { + children = children.concat(oldError.children.slice(0)); + } + oldError.children = undefined; + children.push(oldError); + return false; + } + return true; + }); + if (children.length) { + err.children = children; + } + newErrors.push(err); + }); + + return newErrors; +} + +module.exports = validateSchema; diff --git a/dist/utils/webpack-generator.js b/dist/utils/webpack-generator.js new file mode 100644 index 00000000000..561f10931f7 --- /dev/null +++ b/dist/utils/webpack-generator.js @@ -0,0 +1,77 @@ +var path = require("path"); +var mkdirp = require("mkdirp"); +var Generator = require("yeoman-generator"); +var copyUtils = require("../utils/copy-utils"); + +/** + * Creates a Yeoman Generator that generates a project conforming + * to webpack-defaults. + * + * @param {any[]} prompts An array of Yeoman prompt objects + * + * @param {string} templateDir Absolute path to template directory + * + * @param {string[]} copyFiles An array of file paths (relative to `./templates`) + * of files to be copied to the generated project. File paths should be of the + * form `path/to/file.js.tpl`. + * + * @param {string[]} copyTemplateFiles An array of file paths (relative to + * `./templates`) of files to be copied to the generated project. Template + * file paths should be of the form `path/to/_file.js.tpl`. + * + * @param {Function} templateFn A function that is passed a generator instance and + * returns an object containing data to be supplied to the template files. + * + * @returns {Generator} A class extending Generator + */ +function webpackGenerator( + prompts, + templateDir, + copyFiles, + copyTemplateFiles, + templateFn +) { + //eslint-disable-next-line + return class extends Generator { + prompting() { + return this.prompt(prompts).then(props => { + this.props = props; + }); + } + + default() { + var currentDirName = path.basename(this.destinationPath()); + if (currentDirName !== this.props.name) { + this.log(` + Your project must be inside a folder named ${this.props.name} + I will create this folder for you. + `); + mkdirp(this.props.name); + var pathToProjectDir = this.destinationPath(this.props.name); + this.destinationRoot(pathToProjectDir); + } + } + + writing() { + this.copy = copyUtils.generatorCopy(this, templateDir); + this.copyTpl = copyUtils.generatorCopyTpl( + this, + templateDir, + templateFn(this) + ); + + copyFiles.forEach(this.copy); + copyTemplateFiles.forEach(this.copyTpl); + } + + install() { + this.npmInstall(["webpack-defaults", "bluebird"], { + "save-dev": true + }).then(() => { + this.spawnCommand("npm", ["run", "webpack-defaults"]); + }); + } + }; +} + +module.exports = webpackGenerator; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 00000000000..19fe632aaa8 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,44 @@ +"use strict"; + +const path = require("path"); + +/* +* @function initialize +* +* First function to be called after running a flag. This is a check, +* to match the flag with the respective require. +* +* @param { String } command - which feature to use +* @param { Object } args - arguments from the CLI +* @returns { Module } returns the module with the command +* +*/ + +module.exports = function initialize(command, args) { + const popArgs = args.slice(2).pop(); + switch (command) { + case "init": { + const initPkgs = args.slice(2).length === 1 ? [] : [popArgs]; + //eslint-disable-next-line + return require("./init.js")(initPkgs); + } + case "migrate": { + const filePaths = args.slice(2).length === 1 ? [] : [popArgs]; + if (!filePaths.length) { + throw new Error("Please specify a path to your webpack config"); + } + const inputConfigPath = path.resolve(process.cwd(), filePaths[0]); + //eslint-disable-next-line + return require("./migrate.js")(inputConfigPath, inputConfigPath); + } + case "generate-loader": { + return require("./generate-loader/index.js")(); + } + case "generate-plugin": { + return require("./generate-plugin/index.js")(); + } + default: { + throw new Error(`Unknown command ${command} found`); + } + } +}; diff --git a/lib/init.js b/lib/init.js new file mode 100644 index 00000000000..732d36ac525 --- /dev/null +++ b/lib/init.js @@ -0,0 +1,22 @@ +"use strict"; + +const npmPackagesExists = require("./utils/npm-packages-exists"); +const creator = require("./creator/index").creator; + +/* +* @function initializeInquirer +* +* First function to be called after running the init flag. This is a check, +* if we are running the init command with no arguments or if we got dependencies +* +* @param { Object } pkg - packages included when running the init command +* @returns { } creator|npmPackagesExists - returns an installation of the package, +* followed up with a yeoman instance of that if there's packages. If not, it creates a defaultGenerator +*/ + +module.exports = function initializeInquirer(pkg) { + if (pkg.length === 0) { + return creator(); + } + return npmPackagesExists(pkg); +}; diff --git a/lib/transformations/__testfixtures__/failing.js b/lib/transformations/__testfixtures__/failing.js index 95de24dcb82..a52f3ffe703 100644 --- a/lib/transformations/__testfixtures__/failing.js +++ b/lib/transformations/__testfixtures__/failing.js @@ -1,81 +1,80 @@ -var webpack = require('webpack'); -var nodeEnvironment = process.env.NODE_ENV -var _ = require('lodash'); +var webpack = require("webpack"); +var nodeEnvironment = process.env.NODE_ENV; +var _ = require("lodash"); var config = { - entry: { - 'lib': './app/index.js', - 'email': './app/email.js' - }, - plugins: [ - new webpack.DefinePlugin({ - 'INCLUDE_ALL_MODULES': function includeAllModulesGlobalFn(modulesArray, application) { - modulesArray.forEach(function executeModuleIncludesFn(moduleFn) { - moduleFn(application); - }); - }, - ENVIRONMENT: JSON.stringify(nodeEnvironment) - }) - ], - output: { - path: __dirname + '/app', - filename: 'bundle.js' - }, - resolve: { - root: __dirname + '/app' - }, - module: { - // preLoaders: [ - // { test: /\.js?$/, loader: 'eslint', exclude: /node_modules/ } - // ], - loaders: [ - { test: /\.js$/, exclude: /(node_modules)/, loader: 'babel' }, - { test: /\.html/, exclude: [/(node_modules)/, /src\/index\.html/], loader: 'html-loader' }, - { test: /\.s?css$/, loader: 'style!css!sass' }, - { test: /\.(png|jpg)$/, loader: 'url-loader?mimetype=image/png' } - ] - }, - // extra configuration options. - // eslint: { - // configFile: '.eslintrc.js' - // } -} + entry: { + lib: "./app/index.js", + email: "./app/email.js" + }, + plugins: [ + new webpack.DefinePlugin({ + INCLUDE_ALL_MODULES: function includeAllModulesGlobalFn(modulesArray, application) { + modulesArray.forEach(function executeModuleIncludesFn(moduleFn) { + moduleFn(application); + }); + }, + ENVIRONMENT: JSON.stringify(nodeEnvironment) + }) + ], + output: { + path: __dirname + "/app", + filename: "bundle.js" + }, + resolve: { + root: __dirname + "/app" + }, + module: { + // preLoaders: [ + // { test: /\.js?$/, loader: 'eslint', exclude: /node_modules/ } + // ], + loaders: [ + { test: /\.js$/, exclude: /(node_modules)/, loader: "babel" }, + { test: /\.html/, exclude: [/(node_modules)/, /src\/index\.html/], loader: "html-loader" }, + { test: /\.s?css$/, loader: "style!css!sass" }, + { test: /\.(png|jpg)$/, loader: "url-loader?mimetype=image/png" } + ] + }, + // extra configuration options. + // eslint: { + // configFile: '.eslintrc.js' + // } +}; switch (nodeEnvironment) { - case 'production': - config.plugins.push(new webpack.optimize.UglifyJsPlugin()); - case 'preproduction': - config.output.path = __dirname + '/dist'; - config.plugins.push(new webpack.optimize.DedupePlugin()); - config.plugins.push(new webpack.optimize.OccurenceOrderPlugin()); + case "production": + config.plugins.push(new webpack.optimize.UglifyJsPlugin()); + case "preproduction": + config.output.path = __dirname + "/dist"; + config.plugins.push(new webpack.optimize.DedupePlugin()); + config.plugins.push(new webpack.optimize.OccurenceOrderPlugin()); - config.output.filename = '[name].js'; + config.output.filename = "[name].js"; - config.entry = { - 'lib': ['./app/index.js', 'angular', 'lodash'], - 'email': ['./app/email.js', 'angular'] - }; + config.entry = { + lib: ["./app/index.js", "angular", "lodash"], + email: ["./app/email.js", "angular"] + }; - config.devtool = 'source-map'; - config.output.libraryTarget = 'commonjs2'; - - break; + config.devtool = "source-map"; + config.output.libraryTarget = "commonjs2"; + break; - case 'test': - config.entry = './index.js'; - break; + case "test": + config.entry = "./index.js"; + break; - case 'development': - config.entry = { - 'lib': ['./app/index.js', 'webpack/hot/dev-server'], - 'email': ['./app/email.js', 'webpack/hot/dev-server'] - }; - config.output.filename = '[name].js'; - config.devtool = 'source-map'; - break; + case "development": + config.entry = { + lib: ["./app/index.js", "webpack/hot/dev-server"], + email: ["./app/email.js", "webpack/hot/dev-server"] + }; + config.output.filename = "[name].js"; + config.devtool = "source-map"; + break; - default: - console.warn('Unknown or Undefined Node Environment. Please refer to package.json for available build commands.'); + default: + console.warn("Unknown or Undefined Node Environment. Please refer to package.json for available build commands."); } -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/lib/transformations/bannerPlugin/bannerPlugin.js b/lib/transformations/bannerPlugin/bannerPlugin.js index 0d78942fa34..0a13a818ac1 100644 --- a/lib/transformations/bannerPlugin/bannerPlugin.js +++ b/lib/transformations/bannerPlugin/bannerPlugin.js @@ -1,12 +1,14 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { Ijscodeshit, INewExpression, IPath } from "../../types"; const utils = require("../utils"); -module.exports = function(j, ast) { +module.exports = function(j: Ijscodeshit, ast: IPath<*>) { return utils .findPluginsByName(j, ast, ["webpack.BannerPlugin"]) - .forEach(path => { - const args = path.value.arguments; + .forEach((path: IPath) => { + const args: any[] = path.value.arguments; // any node // If the first argument is a literal replace it with object notation // See https://webpack.js.org/guides/migrating/#bannerplugin-breaking-change if (args && args.length > 1 && args[0].type === j.Literal.name) { diff --git a/lib/transformations/extractTextPlugin/extractTextPlugin.js b/lib/transformations/extractTextPlugin/extractTextPlugin.js index 67df2f5e65b..622d02e4d25 100644 --- a/lib/transformations/extractTextPlugin/extractTextPlugin.js +++ b/lib/transformations/extractTextPlugin/extractTextPlugin.js @@ -1,26 +1,42 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { + Ijscodeshit, + IPath, + INewExpression, + IObjectExpression, + IMemberExpression, + ILiteral +} from "../../types"; const utils = require("../utils"); -function findInvocation(j, node, pluginName) { - const invocation = +function findInvocation( + j: Ijscodeshit, + node: IPath, + pluginName: string +): boolean { + let found: boolean = false; + found = j(node) .find(j.MemberExpression) .filter(p => p.get("object").value.name === pluginName) .size() > 0; - return invocation; + return found; } -module.exports = function(j, ast) { - const changeArguments = function(p) { - const args = p.value.arguments; +module.exports = function(j: Ijscodeshit, ast: IPath<*>): IPath<*> { + const changeArguments = function(p: IPath) { + const args: any[] = p.value.arguments; // if(args.length === 1) { // return p; // } else - const literalArgs = args.filter(p => utils.isType(p, "Literal")); + const literalArgs: IPath[] = args.filter(p => + utils.isType(p, "Literal") + ); if (literalArgs && literalArgs.length > 1) { - const newArgs = j.objectExpression( - literalArgs.map((p, index) => + const newArgs: IObjectExpression = j.objectExpression( + literalArgs.map((p: IPath, index: number) => utils.createProperty(j, index === 0 ? "fallback" : "use", p.value) ) ); @@ -28,7 +44,7 @@ module.exports = function(j, ast) { } return p; }; - const name = utils.findVariableToPlugin( + const name: ?string = utils.findVariableToPlugin( j, ast, "extract-text-webpack-plugin" @@ -37,6 +53,6 @@ module.exports = function(j, ast) { return ast .find(j.CallExpression) - .filter(p => findInvocation(j, p, name)) + .filter((p: IPath) => findInvocation(j, p, name)) .forEach(changeArguments); }; diff --git a/lib/transformations/index.js b/lib/transformations/index.js index 53d1d38c301..dd202a62f92 100644 --- a/lib/transformations/index.js +++ b/lib/transformations/index.js @@ -1,4 +1,6 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { IPath } from "../types"; const jscodeshift = require("jscodeshift"); const pEachSeries = require("p-each-series"); @@ -30,7 +32,11 @@ const transformations = Object.keys(transformsObject).reduce((res, key) => { return res; }, {}); -function transformSingleAST(ast, source, transformFunction) { +function transformSingleAST( + ast: IPath<*>, + source: string, + transformFunction: Function +): IPath<*> { return new PLazy((resolve, reject) => { setTimeout(() => { try { @@ -53,8 +59,12 @@ function transformSingleAST(ast, source, transformFunction) { * @param { Object } options - Reacst formatting options * @returns { String } Transformed source code * */ -function transform(source, transforms, options) { - const ast = jscodeshift(source); +function transform( + source: string, + transforms: Function[], + options: Object +): string { + const ast: IPath<*> = jscodeshift(source); const recastOptions = Object.assign( { quote: "single" diff --git a/lib/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js b/lib/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js index 09d399f4b72..f661c352bb3 100644 --- a/lib/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js +++ b/lib/transformations/loaderOptionsPlugin/loaderOptionsPlugin.js @@ -1,4 +1,6 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { Ijscodeshit, IPath, IArrayExpression } from "../../types"; const isEmpty = require("lodash/isEmpty"); const findPluginsByName = require("../utils").findPluginsByName; @@ -6,12 +8,12 @@ const createOrUpdatePluginByName = require("../utils") .createOrUpdatePluginByName; const safeTraverse = require("../utils").safeTraverse; -module.exports = function(j, ast) { - const loaderOptions = {}; +module.exports = function(j: Ijscodeshit, ast: IPath<*>) { + const loaderOptions: Object = {}; // If there is debug: true, set debug: true in the plugin // TODO: remove global debug setting - // TODO: I can"t figure out how to find the topmost `debug: true`. help! + // TODO: I can't figure out how to find the topmost `debug: true`. help! if (ast.find(j.Identifier, { name: "debug" }).size()) { loaderOptions.debug = true; } @@ -24,10 +26,10 @@ module.exports = function(j, ast) { return ast .find(j.ArrayExpression) .filter( - path => + (path: IPath<*>) => safeTraverse(path, ["parent", "value", "key", "name"]) === "plugins" ) - .forEach(path => { + .forEach((path: IPath) => { !isEmpty(loaderOptions) && createOrUpdatePluginByName( j, diff --git a/lib/transformations/loaders/loaders.js b/lib/transformations/loaders/loaders.js index 17254c5643f..48509dcc653 100644 --- a/lib/transformations/loaders/loaders.js +++ b/lib/transformations/loaders/loaders.js @@ -1,57 +1,74 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { + Ijscodeshit, + IPath, + IObjectExpression, + IProperty, + IArrayExpression, + ILiteral, + IIdentifier +} from "../../types"; const utils = require("../utils"); -module.exports = function(j, ast) { +module.exports = function(j: Ijscodeshit, ast: IPath<*>): IPath<*> { /** - * Creates an Array expression out of loaders string for syntaxes like - * - * { - * loader: 'style!css` - * } - * - * or - * - * { - * loaders: ['style', 'css'] - * } - * - * or - * - * loaders: [{ - * loader: 'style' - * }, - * { - * loader: 'css', - * }] - * - * it should generate - * - * { - * use: [{ - * loader: 'style' - * }, - * { - * loader: 'css' - * }] - * } - * @param {NodeObject} path Must be an ObjectExpression - * @returns {NodeObject} The created array expression - */ - const createArrayExpressionFromArray = function(path) { + * Creates an Array expression out of loaders string + * + * + * For syntaxes like + * + * { + * loader: 'style!css` + * } + * + * or + * + * { + * loaders: ['style', 'css'] + * } + * + * or + * + * loaders: [{ + * loader: 'style' + * }, + * { + * loader: 'css', + * }] + * + * it should generate + * + * { + * use: [{ + * loader: 'style' + * }, + * { + * loader: 'css' + * }] + * } + * @param {Node} path Must be an ObjectExpression + * @return {Node} [] + */ + const createArrayExpressionFromArray = function( + path: IPath + ): IPath<*> { + const value: IObjectExpression = path.value; // Find paths with `loaders` keys in the given Object - const paths = path.value.properties.filter(prop => + const paths = value.properties.filter((prop: IProperty<*>) => prop.key.name.startsWith("loader") ); // For each pair of key and value - paths.forEach(pair => { + paths.forEach((pair: IProperty<*>) => { // Replace 'loaders' Identifier with 'use' pair.key.name = "use"; // If the value is an Array if (pair.value.type === j.ArrayExpression.name) { // replace its elements + const pairValue: IArrayExpression = pair.value; pair.value = j.arrayExpression( - pair.value.elements.map(arrElement => { + pairValue.elements.map(arrElement => { // If items of the array are Strings if (arrElement.type === j.Literal.name) { // Replace with `{ loader: LOADER }` Object @@ -66,8 +83,9 @@ module.exports = function(j, ast) { // If the value is String of loaders like 'style!css' } else if (pair.value.type === j.Literal.name) { // Replace it with Array expression of loaders + const literalValue: ILiteral = pair.value; pair.value = j.arrayExpression( - pair.value.value.split("!").map(loader => { + literalValue.value.split("!").map(loader => { return j.objectExpression([ utils.createProperty(j, "loader", loader) ]); @@ -78,7 +96,9 @@ module.exports = function(j, ast) { return path; }; - const createLoaderWithQuery = p => { + const createLoaderWithQuery = ( + p: IPath + ): IObjectExpression => { let properties = p.value.properties; let loaderValue = properties.reduce( (val, prop) => (prop.key.name === "loader" ? prop.value.value : val), @@ -86,14 +106,14 @@ module.exports = function(j, ast) { ); let loader = loaderValue.split("?")[0]; let query = loaderValue.split("?")[1]; - let options = query.split("&").map(option => { + let options: Object[] = query.split("&").map(option => { const param = option.split("="); const key = param[0]; const val = param[1] || true; // No value in query string means it is truthy value return j.objectProperty(j.identifier(key), utils.createLiteral(j, val)); }); - let loaderProp = utils.createProperty(j, "loader", loader); - let queryProp = j.property( + let loaderProp: IProperty<*> = utils.createProperty(j, "loader", loader); + let queryProp: IProperty = j.property( "init", j.identifier("options"), j.objectExpression(options) @@ -101,7 +121,7 @@ module.exports = function(j, ast) { return j.objectExpression([loaderProp, queryProp]); }; - const findLoaderWithQueryString = p => { + const findLoaderWithQueryString = (p: IPath): boolean => { return p.value.properties.reduce((predicate, prop) => { return ( (utils.safeTraverse(prop, ["value", "value", "indexOf"]) && @@ -112,12 +132,12 @@ module.exports = function(j, ast) { }; /** - * If the path value is `loaders` and it's located in `module` object - * we assume it's the loader's section - * - * @param {NodeObject} path Must be an ObjectExpression - * @return {tbd} tbd - */ + * If the path value is `loaders` and it's located in `module` object + * we assume it's the loader's section + * + * @param {Node} path [description] + * @return {Node} [description] + */ const checkForLoader = path => path.value.name === "loaders" && utils.safeTraverse(path, [ @@ -130,11 +150,14 @@ module.exports = function(j, ast) { ]) === "module"; /** - * Puts node path that is pre- or postLoader into `enforce` key - * @param {object} p tbd - * @returns {object} A new object that contains the pre/post loaders in the enforce key of the loader - */ - const fitIntoLoaders = p => { + * Puts node path that is pre- or postLoader into `enforce` key + * + * @param {Node} p [] + * @returns {*} [] + */ + const fitIntoLoaders = ( + p: IPath + ): IPath => { let loaders; p.value.properties.map(prop => { const keyName = prop.key.name; @@ -165,31 +188,35 @@ module.exports = function(j, ast) { }; /** - * Find pre and postLoaders - * @returns {*} All the pre and post loaders - */ - const prepostLoaders = () => + * Find pre and postLoaders + * + * @param {[type]} IPath [description] + * @return {[type]} [description] + */ + const prepostLoaders = (): IPath => ast .find(j.ObjectExpression) .filter(p => utils.findObjWithOneOfKeys(p, ["preLoaders", "postLoaders"])) .forEach(fitIntoLoaders); /** - * Convert top level `loaders` to `rules` - * See https://webpack.js.org/configuration/module/#module-rules - * @returns {Array} An array of object of all the rules - */ - const loadersToRules = () => + * Convert top level `loaders` to `rules` + * See https://webpack.js.org/configuration/module/#module-rules + * @param {[type]} IPath [description] + * @return {[type]} [description] + */ + const loadersToRules = (): IPath => ast .find(j.Identifier) .filter(checkForLoader) - .forEach(p => (p.value.name = "rules")); + .forEach((p: IPath) => (p.value.name = "rules")); /** - * Converts 'loader' and 'loaders' to Array of {Rule.Use} - * @returns {Array} An array of objects of all the rules - */ - const loadersToArrayExpression = () => + * Converts 'loader' and 'loaders' to Array of {Rule.Use} + * + * @returns {Node} [] + */ + const loadersToArrayExpression = (): IPath => ast .find(j.ObjectExpression) .filter(path => utils.findObjWithOneOfKeys(path, ["loader", "loaders"])) @@ -206,59 +233,61 @@ module.exports = function(j, ast) { .forEach(createArrayExpressionFromArray); /** - * Finds loaders with options encoded as query string and replaces it with options obejct - * - * i.e. for loader like - * - * { - * loader: 'css?modules&importLoaders=1&string=test123' - * } - * - * it should generate - * { - * loader: 'css-loader', - * options: { - * modules: true, - * importLoaders: 1, - * string: 'test123' - * } - * } - * @returns {Object} An object of the generated loader settings - */ - const loaderWithQueryParam = () => + * Finds loaders with options encoded as query string and replaces it with options obejct + * + * i.e. for loader like + * + * { + * loader: 'css?modules&importLoaders=1&string=test123' + * } + * + * it should generate + * { + * loader: 'css-loader', + * options: { + * modules: true, + * importLoaders: 1, + * string: 'test123' + * } + * } + * + * @type {[type]} + * @returns {Node} [] + */ + const loaderWithQueryParam = (): IPath => ast .find(j.ObjectExpression) - .filter(p => utils.findObjWithOneOfKeys(p, "loader")) + .filter(p => utils.findObjWithOneOfKeys(p, ["loader"])) .filter(findLoaderWithQueryString) .replaceWith(createLoaderWithQuery); /** - * Finds nodes with `query` key and replaces it with `options` - * - * i.e. for - * { - * query: { ... } - * } - * - * it should generate - * - * { - * options: { ... } - * } - * @returns {Object} An object with the replaced query into options - */ - const loaderWithQueryProp = () => + * Finds nodes with `query` key and replaces it with `options` + * + * i.e. for + * { + * query: { ... } + * } + * + * it should generate + * + * { + * options: { ... } + * } + * @returns {Node} [] + */ + const loaderWithQueryProp = (): IPath => ast .find(j.Identifier) .filter(p => p.value.name === "query") .replaceWith(j.identifier("options")); /** - * Adds required `-loader` suffix to loader with missing suffix - * i.e. for `babel` it should generate `babel-loader` - * @returns {object} The new object that has all the `-loader` sufixes - */ - const addLoaderSuffix = () => + * Adds required `-loader` suffix to loader with missing suffix + * i.e. for `babel` it should generate `babel-loader` + * @returns {Node} [] + */ + const addLoaderSuffix = (): IPath => ast.find(j.ObjectExpression).forEach(path => { path.value.properties.forEach(prop => { if ( diff --git a/lib/transformations/outputPath/outputPath.js b/lib/transformations/outputPath/outputPath.js index 39e28ef4ea9..3cd5b1a928a 100644 --- a/lib/transformations/outputPath/outputPath.js +++ b/lib/transformations/outputPath/outputPath.js @@ -1,34 +1,36 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { Ijscodeshit, ICallExpression, IPath } from "../../types"; const utils = require("../utils"); -module.exports = function(j, ast) { +module.exports = function(j: Ijscodeshit, ast: IPath<*>): IPath<*> { const literalOutputPath = ast .find(j.ObjectExpression) .filter( - p => + (p: IPath<*>) => utils.safeTraverse(p, ["parentPath", "value", "key", "name"]) === "output" ) .find(j.Property) .filter( - p => + (p: IPath<*>) => utils.safeTraverse(p, ["value", "key", "name"]) === "path" && utils.safeTraverse(p, ["value", "value", "type"]) === "Literal" ); if (literalOutputPath) { - let pathVarName = "path"; - let isPathPresent = false; + let pathVarName: ?string = "path"; + let isPathPresent: boolean = false; const pathDecalaration = ast .find(j.VariableDeclarator) .filter( - p => + (p: IPath<*>) => utils.safeTraverse(p, ["value", "init", "callee", "name"]) === "require" ) .filter( - p => + (p: IPath<*>) => utils.safeTraverse(p, ["value", "init", "arguments"]) && p.value.init.arguments.reduce((isPresent, a) => { return (a.type === "Literal" && a.value === "path") || isPresent; @@ -41,13 +43,13 @@ module.exports = function(j, ast) { pathVarName = utils.safeTraverse(p, ["value", "id", "name"]); }); } - + const finalPathName: string = (pathVarName: any); literalOutputPath .find(j.Literal) - .replaceWith(p => replaceWithPath(j, p, pathVarName)); + .replaceWith((p: IPath<*>) => replaceWithPath(j, p, finalPathName)); if (!isPathPresent) { - const pathRequire = utils.getRequire(j, "path", "path"); + const pathRequire: string = (utils.getRequire(j, "path", "path"): any); return ast .find(j.Program) .replaceWith(p => @@ -58,7 +60,11 @@ module.exports = function(j, ast) { return ast; }; -function replaceWithPath(j, p, pathVarName) { +function replaceWithPath( + j: Ijscodeshit, + p: IPath<*>, + pathVarName: string +): IPath { const convertedPath = j.callExpression( j.memberExpression(j.identifier(pathVarName), j.identifier("join"), false), [j.identifier("__dirname"), p.value] diff --git a/lib/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js b/lib/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js index 97ca0a0b239..24b43447a12 100644 --- a/lib/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js +++ b/lib/transformations/removeDeprecatedPlugins/removeDeprecatedPlugins.js @@ -1,28 +1,33 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { Ijscodeshit, IPath } from "../../types"; const codeFrame = require("babel-code-frame"); const chalk = require("chalk"); const utils = require("../utils"); -const example = `plugins: [ +const example: string = `plugins: [ new webpack.optimize.OccurrenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin(), new webpack.optimize.DedupePlugin() ]`; -module.exports = function(j, ast, source) { +module.exports = function(j: Ijscodeshit, ast: IPath<*>, source: string) { // List of deprecated plugins to remove // each item refers to webpack.optimize.[NAME] construct - const deprecatedPlugingsList = [ + const deprecatedPlugingsList: string[] = [ "webpack.optimize.OccurrenceOrderPlugin", "webpack.optimize.DedupePlugin" ]; return utils .findPluginsByName(j, ast, deprecatedPlugingsList) - .forEach(path => { + .forEach((path: IPath<*>) => { // For now we only support the case there plugins are defined in an Array - const arrayPath = utils.safeTraverse(path, ["parent", "value"]); + const arrayPath: ?IPath<*> = utils.safeTraverse(path, [ + "parent", + "value" + ]); if (arrayPath && utils.isType(arrayPath, "ArrayExpression")) { // Check how many plugins are defined and // if there is only last plugin left remove `plugins: []` node @@ -34,17 +39,21 @@ module.exports = function(j, ast, source) { } } else { const startLoc = path.value.loc.start; - const messageOutput = `${chalk.red( - "Only plugins instantiated in the array can be automatically removed i.e.:" - )} - ${codeFrame(example, null, null, { highlightCode: true })} + console.log(` +${chalk.red( + "Only plugins instantiated in the array can be automatically removed i.e.:" + )} + +${codeFrame(example, null, null, { highlightCode: true })} + ${chalk.red("but you use it like this:")} - ${codeFrame(source, startLoc.line, startLoc.column, { highlightCode: true })} + +${codeFrame(source, startLoc.line, startLoc.column, { highlightCode: true })} + ${chalk.red("Please remove deprecated plugins manually. ")} See ${chalk.underline( "https://webpack.js.org/guides/migrating/" - )} for more information.`; - console.log(messageOutput); + )} for more information.`); } }); }; diff --git a/lib/transformations/removeJsonLoader/removeJsonLoader.js b/lib/transformations/removeJsonLoader/removeJsonLoader.js index a222b35bb1a..79914586ca7 100644 --- a/lib/transformations/removeJsonLoader/removeJsonLoader.js +++ b/lib/transformations/removeJsonLoader/removeJsonLoader.js @@ -1,13 +1,20 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { + Ijscodeshit, + IPath, + ILiteral, + IArrayExpression +} from "../../types"; const utils = require("../utils"); -module.exports = function(j, ast) { - function removeLoaderByName(path, name) { - const loadersNode = path.value.value; +module.exports = function(j: Ijscodeshit, ast: IPath<*>) { + function removeLoaderByName(path: IPath<*>, name: string) { + const loadersNode: Object = path.value.value; switch (loadersNode.type) { case j.ArrayExpression.name: { - let loaders = loadersNode.elements.map(p => { + let loaders = (loadersNode: IArrayExpression).elements.map(p => { return utils.safeTraverse(p, ["properties", "0", "value", "value"]); }); const loaderIndex = loaders.indexOf(name); @@ -27,7 +34,7 @@ module.exports = function(j, ast) { case j.Literal.name: { // If only the loader with the matching name was used // we can remove the whole Property node completely - if (loadersNode.value === name) { + if ((loadersNode: ILiteral).value === name) { j(path.parent).remove(); } break; @@ -35,22 +42,14 @@ module.exports = function(j, ast) { } } - function removeLoaders(ast) { + function removeLoaders(ast: IPath<*>) { ast - .find(j.Property, { - key: { - name: "use" - } - }) - .forEach(path => removeLoaderByName(path, "json-loader")); + .find(j.Property, { key: { name: "use" } }) + .forEach((path: IPath<*>) => removeLoaderByName(path, "json-loader")); ast - .find(j.Property, { - key: { - name: "loader" - } - }) - .forEach(path => removeLoaderByName(path, "json-loader")); + .find(j.Property, { key: { name: "loader" } }) + .forEach((path: IPath<*>) => removeLoaderByName(path, "json-loader")); } const transforms = [removeLoaders]; diff --git a/lib/transformations/resolve/resolve.js b/lib/transformations/resolve/resolve.js index 6c91c54280a..e6ec9ab915b 100644 --- a/lib/transformations/resolve/resolve.js +++ b/lib/transformations/resolve/resolve.js @@ -1,7 +1,17 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { + Ijscodeshit, + IPath, + IProperty, + IObjectExpression, + IArrayExpression +} from "../../types"; -module.exports = function transformer(j, ast) { - const getRootVal = p => { +module.exports = function transformer(j: Ijscodeshit, ast: IPath<*>) { + const getRootVal = ( + p: IPath> + ): IProperty => { return p.node.value.properties.filter(prop => prop.key.name === "root")[0]; }; @@ -11,14 +21,16 @@ module.exports = function transformer(j, ast) { }, -1); }; - const isModulePresent = p => { + const isModulePresent = (p: IPath>) => { const modules = p.node.value.properties.filter( prop => prop.key.name === "modules" ); return modules.length > 0 && modules[0]; }; - const createModuleArray = p => { + const createModuleArray = ( + p: IPath> + ): IPath> => { const rootVal = getRootVal(p); let modulesVal = null; if (rootVal.value.type === "ArrayExpression") { @@ -45,7 +57,7 @@ module.exports = function transformer(j, ast) { return ast .find(j.Property) - .filter(p => { + .filter((p: IPath>) => { return ( p.node.key.name === "resolve" && p.node.value.properties.filter(prop => prop.key.name === "root") diff --git a/lib/transformations/uglifyJsPlugin/uglifyJsPlugin.js b/lib/transformations/uglifyJsPlugin/uglifyJsPlugin.js index a4e71398126..cf0acdd6b1f 100644 --- a/lib/transformations/uglifyJsPlugin/uglifyJsPlugin.js +++ b/lib/transformations/uglifyJsPlugin/uglifyJsPlugin.js @@ -1,8 +1,10 @@ -"use strict"; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { Ijscodeshit, IPath } from "../../types"; const findPluginsByName = require("../utils").findPluginsByName; -module.exports = function(j, ast) { +module.exports = function(j: Ijscodeshit, ast: IPath<*>) { function createSourceMapsProperty() { return j.property("init", j.identifier("sourceMap"), j.identifier("true")); } diff --git a/lib/transformations/utils.js b/lib/transformations/utils.js index 5c3795d4f25..d9d46240f50 100644 --- a/lib/transformations/utils.js +++ b/lib/transformations/utils.js @@ -1,8 +1,19 @@ -"use strict"; - -function safeTraverse(obj, paths) { - let val = obj; - let idx = 0; +// @flow +// eslint-disable-next-line node/no-unsupported-features +import type { + Ijscodeshit, + IPath, + IProperty, + ILiteral, + IIdentifier, + IVariableDeclarator, + IObjectExpression, + IArrayExpression +} from "../types"; + +function safeTraverse(obj: Object, paths: string[]): ?any { + let val: Object = obj; + let idx: number = 0; while (idx < paths.length) { if (!val) { @@ -15,7 +26,7 @@ function safeTraverse(obj, paths) { } // Convert nested MemberExpressions to strings like webpack.optimize.DedupePlugin -function memberExpressionToPathString(path) { +function memberExpressionToPathString(path: Object): string { if (path && path.object) { return [memberExpressionToPathString(path.object), path.property.name].join( "." @@ -25,7 +36,7 @@ function memberExpressionToPathString(path) { } // Convert Array like ['webpack', 'optimize', 'DedupePlugin'] to nested MemberExpressions -function pathsToMemberExpression(j, paths) { +function pathsToMemberExpression(j: Ijscodeshit, paths: string[]) { if (!paths.length) { return null; } else if (paths.length === 1) { @@ -47,10 +58,14 @@ function pathsToMemberExpression(j, paths) { * * @param j — jscodeshift API * @param { Node } node - Node to start search from -* @param { Array } pluginNamesArray - Array of plugin names like `webpack.optimize.LoaderOptionsPlugin` +* @param { Array } pluginNamesArray - Array of plugin names like `webpack.LoaderOptionsPlugin` * @returns Path * */ -function findPluginsByName(j, node, pluginNamesArray) { +function findPluginsByName( + j: Ijscodeshit, + node: IPath<*>, + pluginNamesArray: string[] +): IPath<*> { return node.find(j.NewExpression).filter(path => { return pluginNamesArray.some( plugin => @@ -68,12 +83,11 @@ function findPluginsByName(j, node, pluginNamesArray) { * @param { Node } node - Node to start search from * @returns Path * */ -function findPluginsRootNodes(j, node) { - return node.find(j.Property, { - key: { - name: "plugins" - } - }); +function findPluginsRootNodes( + j: Ijscodeshit, + node: IPath<*> +): IPath> { + return node.find(j.Property, { key: { name: "plugins" } }); } /* @@ -86,7 +100,11 @@ function findPluginsRootNodes(j, node) { * @param { string | number | boolean } value - Property value * @returns Node * */ -function createProperty(j, key, value) { +function createProperty( + j: Ijscodeshit, + key: string | number, + value: any +): IProperty<*> { return j.property( "init", createIdentifierOrLiteral(j, key), @@ -104,8 +122,11 @@ function createProperty(j, key, value) { * @returns { Node } * */ -function createLiteral(j, val) { - let literalVal = val; +function createLiteral( + j: Ijscodeshit, + val: string | boolean | number +): IPath { + let literalVal: any = val; // We'll need String to native type conversions if (typeof val === "string") { // 'true' => true @@ -128,7 +149,8 @@ function createLiteral(j, val) { * @returns { Node } * */ -function createIdentifierOrLiteral(j, val) { +function createIdentifierOrLiteral(j: Ijscodeshit, val: any): any { + // IPath | IPath doesn't work, find another way let literalVal = val; // We'll need String to native type conversions if (typeof val === "string" || val.__paths) { @@ -166,11 +188,16 @@ function createIdentifierOrLiteral(j, val) { * * @param j — jscodeshift API * @param { NodePath } rooNodePath - `plugins: []` NodePath where plugin should be added. See https://github.com/facebook/jscodeshift/wiki/jscodeshift-Documentation#nodepaths - * @param { string } pluginName - ex. `webpack.optimize.LoaderOptionsPlugin` + * @param { string } pluginName - ex. `webpack.LoaderOptionsPlugin` * @param { Object } options - plugin options * @returns void * */ -function createOrUpdatePluginByName(j, rootNodePath, pluginName, options) { +function createOrUpdatePluginByName( + j: Ijscodeshit, + rootNodePath: IPath<*>, + pluginName: string, + options: any +) { const pluginInstancePath = findPluginsByName(j, j(rootNodePath), [ pluginName ]); @@ -194,7 +221,7 @@ function createOrUpdatePluginByName(j, rootNodePath, pluginName, options) { .find(j.ObjectExpression) .get("properties"); - optionsProps.forEach(opt => { + optionsProps.forEach((opt: any) => { // Search for same keys in the existing object const existingProps = j(currentProps) .find(j.Identifier) @@ -240,8 +267,12 @@ function createOrUpdatePluginByName(j, rootNodePath, pluginName, options) { * @returns { string } variable name - ex. 'var s = require(s) gives "s"` * */ -function findVariableToPlugin(j, rootNode, pluginPackageName) { - const moduleVarNames = rootNode +function findVariableToPlugin( + j: Ijscodeshit, + rootNode: IPath<*>, + pluginPackageName: string +): ?string { + const moduleVarNames: IVariableDeclarator[] = rootNode .find(j.VariableDeclarator) .filter(j.filters.VariableDeclarator.requiresModule(pluginPackageName)) .nodes(); @@ -258,11 +289,11 @@ function findVariableToPlugin(j, rootNode, pluginPackageName) { * @returns {boolean} */ -function isType(path, type) { +function isType(path: IPath<*>, type: string): boolean { return path.type === type; } -function findObjWithOneOfKeys(p, keyNames) { +function findObjWithOneOfKeys(p: IPath<*>, keyNames: string[]): boolean { return p.value.properties.reduce((predicate, prop) => { const name = prop.key.name; return keyNames.indexOf(name) > -1 || predicate; @@ -279,7 +310,11 @@ function findObjWithOneOfKeys(p, keyNames) { * @returns {NodePath} - the created ast */ -function getRequire(j, constName, packagePath) { +function getRequire( + j: Ijscodeshit, + constName: string, + packagePath: string +): IPath { return j.variableDeclaration("const", [ j.variableDeclarator( j.identifier(constName), @@ -299,7 +334,12 @@ function getRequire(j, constName, packagePath) { * @returns - nothing */ -function checkIfExistsAndAddValue(j, node, key, value) { +function checkIfExistsAndAddValue( + j: Ijscodeshit, + node: IPath, + key: string, + value: string +): void { const existingProp = node.value.properties.filter( prop => prop.key.name === key ); @@ -321,7 +361,10 @@ function checkIfExistsAndAddValue(j, node, key, value) { * @param { String } key - st name * @returns - { Array } arr - An empty array */ -function createEmptyArrayProperty(j, key) { +function createEmptyArrayProperty( + j: Ijscodeshit, + key: string +): IProperty { return j.property("init", j.identifier(key), j.arrayExpression([])); } @@ -332,13 +375,19 @@ function createEmptyArrayProperty(j, key) { * @param j — jscodeshift API * @param { String } key - object name * @param { string } subProps - computed value of the property +* @param { Boolean } shouldDropKeys - * @returns - { Array } arr - An array with the object properties */ -function createArrayWithChildren(j, key, subProps, shouldDropKeys) { +function createArrayWithChildren( + j: Ijscodeshit, + key: string, + subProps: { [string]: any }, + shouldDropKeys: boolean +) { let arr = createEmptyArrayProperty(j, key); if (shouldDropKeys) { - subProps.forEach(subProperty => { + subProps.forEach((subProperty: IPath<*>) => { let objectOfArray = j.objectExpression([]); if (typeof subProperty !== "string") { loopThroughObjects(j, objectOfArray, subProperty); @@ -370,10 +419,19 @@ function createArrayWithChildren(j, key, subProps, shouldDropKeys) { * nothing is left */ -function loopThroughObjects(j, p, obj) { +function loopThroughObjects( + j: Ijscodeshit, + p: IObjectExpression, + obj: Object +): void { Object.keys(obj).forEach(prop => { if (prop.indexOf("inject") >= 0) { - return p.properties.push(createIdentifierOrLiteral(j, obj[prop])); + // TODO to insert the type of node, identifier or literal + const propertyExpression: IPath<*> = createIdentifierOrLiteral( + j, + obj[prop] + ); + return p.properties.push(propertyExpression); } // eslint-disable-next-line no-irregular-whitespace if (typeof obj[prop] !== "object" || obj[prop] instanceof RegExp) { @@ -410,7 +468,11 @@ function loopThroughObjects(j, p, obj) { * @returns - { Node } - An property with the supplied property */ -function createObjectWithSuppliedProperty(j, key, prop) { +function createObjectWithSuppliedProperty( + j: Ijscodeshit, + key: string, + prop: any +): IProperty<*> { return j.property("init", j.identifier(key), prop); } @@ -423,7 +485,7 @@ function createObjectWithSuppliedProperty(j, key, prop) { * @returns - { Node } - A literal node with the found regexp */ -function createExternalRegExp(j, prop) { +function createExternalRegExp(j: Ijscodeshit, prop: any): IPath { let regExpProp = prop.__paths[0].value.program.body[0].expression; return j.literal(regExpProp.value); } @@ -439,8 +501,13 @@ function createExternalRegExp(j, prop) { * @returns - { Node } - Returns node the pushed property */ -function pushCreateProperty(j, p, key, val) { - let property; +function pushCreateProperty( + j: Ijscodeshit, + p: IPath, + key: string, + val: any +): number { + let property: string | IPath | IPath; if (val.hasOwnProperty("type")) { property = val; } else { @@ -462,7 +529,12 @@ function pushCreateProperty(j, p, key, val) { *subProperty is an array, else it will invoke a function that will push a single node */ -function pushObjectKeys(j, p, webpackProperties, name) { +function pushObjectKeys( + j: Ijscodeshit, + p: IPath<*>, + webpackProperties: Object, + name: string +): any { p.value.properties .filter(n => safeTraverse(n, ["key", "name"]) === name) .forEach(prop => { @@ -518,7 +590,13 @@ function pushObjectKeys(j, p, webpackProperties, name) { * @returns - { Function } cb - Returns the callback and pushes a new node */ -function isAssignment(j, p, cb, identifier, property) { +function isAssignment( + j: Ijscodeshit, + p: IPath<*>, + cb: () => void, + identifier: string, + property: Object +): any { if (p.parent.value.type === "AssignmentExpression") { if (j) { return cb(j, p, identifier, property); diff --git a/lib/types.js b/lib/types.js new file mode 100644 index 00000000000..b6edbcbe147 --- /dev/null +++ b/lib/types.js @@ -0,0 +1,192 @@ +// @flow +/* eslint-disable node/no-unsupported-features */ + +export type Ijscodeshit = { + literal: (literalVal: string | boolean | number) => IPath, + identifier: (literalVal: string | boolean | number) => IPath, + property: (name: string, key: any, assignee: any) => IProperty<*>, + memberExpression: (first: any, second: any) => any, + (): IPath<*>, + // (node: IPath<*>): IPath<*>, + objectExpression: (options: any[]) => IObjectExpression, + arrayExpression: () => any, + program: (paths: string[]) => IPath<*>, + callExpression: () => IPath, + objectProperty: ( + node: any, + objectLiteral?: Object + ) => IPath, + filters: { + VariableDeclarator: { + requiresModule: (moduelToRequire: any) => () => void + } + }, + variableDeclaration: ( + declarationName: string, + declaratino: any + ) => IPath, + variableDeclarator: ( + declarator: any, + expression: any + ) => IPath, + newExpression: (rightExpression: any, leftExpressino: any) => IIdentifier, + + // primitives expressions + NewExpression: Expression, + MemberExpression: Expression, + CallExpression: Expression, + ObjectExpression: Expression, + Literal: Expression, + Property: Expression, + VariableDeclarator: Expression, + Identifier: Expression, + ArrayExpression: Expression, + Program: Expression +}; + +export type Expression = { + name: string +}; + +export type IPath = { + value: NodeType, + parent: IPath<*>, + node: NodeType, + type: string, + find: (memberToFind: Expression) => IPath<*>, + size: () => number, + filter: (callback: (node: IPath<*>) => any) => IPath<*>, + forEach: (callback: (node: IPath<*>) => any | void) => IPath<*>, + replaceWith: any => IPath<*>, + toSource: (options?: Object) => IPath<*>, + get: (property: string) => any, + remove: (node: any) => void, + nodes: () => any[] +}; +/** + * [IAstNodeCommentLine description] + * @type {Object} + */ +export type IAstNodeCommentLine = { + type: string, + value: string, + start: number, + end: number, + loc: ILoc, + rage: number[] +}; + +/** + * [ILoc description] + * @type {Object} + */ +type ILoc = { + start: { + line: number, + column: number + }, + end: { + line: number, + column: number + } +}; + +/** + * [ITokenType description] + * @type {Object} + */ +export type ITokenType = { + loc: ILoc, + end: number, + start: number, + type: { + label: string, + keyword: string, + beforeExpr: boolean, + startsExpr: boolean, + rightAssociative: boolean, + isLoop: boolean, + isAssign: boolean, + prefix: boolean, + postfix: boolean, + binop: any, + updateContext: any + } +}; + +export type ILiteral = { + type: string, + start: number, + end: number, + loc: ILoc, + value: string, + rawValue: string, + raw: string, + name: string +}; + +export type INewExpression = { + arguments: any[] +}; + +export type IObjectExpression = { + properties: any[], + type: string, + range: [number, number], + loc: ILoc +}; + +export type IProperty = { + type: string, + key: IIdentifier, + computed: boolean, + kind: string, + value: any, // any node + loc: ILoc, + value: NodeType +}; + +export type IIdentifier = { + name: string, + type: string, + loc: ILoc, + range: [number, number] +}; + +export type IArrayExpression = { + elements: any[], // ILiteral[] | IObjectExpression[] + value: any // any node +}; + +export type IMemberExpression = { + type: string, + computed: boolean, + object: IIdentifier, + property: IIdentifier, + range: [number, number], + loc: ILoc +}; + +export type IVariableDeclaration = { + declaration: IVariableDeclarator[], + type: string, + kind: string, + range: [number, number], + loc: ILoc +}; + +export type IVariableDeclarator = { + type: string, + id: IIdentifier, + init: any, + range: [number, number], + loc: ILoc +}; + +export type ICallExpression = { + type: string, + callee: IMemberExpression, + arguments?: any[], + range: [number, number], + loc: ILoc +}; diff --git a/package-lock.json b/package-lock.json index 64aa32271f5..cd9f9bcc2ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "webpack-cli", - "version": "1.4.0", + "version": "1.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1092,6 +1092,15 @@ "babel-plugin-transform-regenerator": "6.26.0" } }, + "babel-preset-flow": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", + "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "dev": true, + "requires": { + "babel-plugin-transform-flow-strip-types": "6.22.0" + } + }, "babel-preset-jest": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-20.0.3.tgz", @@ -2368,6 +2377,15 @@ } } }, + "eslint-plugin-flowtype": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.38.0.tgz", + "integrity": "sha512-bxde09/khzLyKLO1Mu4Mm2MpHURG2Ml2W5YbgGnD8sNJmukDT8MWARvo4ftIQ3HEnIWcZqNWAp5uF2dmrg9kMw==", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + }, "eslint-plugin-node": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.0.tgz", @@ -2692,11 +2710,27 @@ "write": "0.2.1" } }, + "flow-bin": { + "version": "0.49.1", + "resolved": "https://registry.npmjs.org/flow-bin/-/flow-bin-0.49.1.tgz", + "integrity": "sha1-yeRWsxc6dTWk/68olWNSxju44+k=", + "dev": true + }, "flow-parser": { "version": "0.56.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.56.0.tgz", "integrity": "sha1-jvHDJfUAAEk8p2TD7N0PLHGpxSU=" }, + "flow-remove-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-1.2.1.tgz", + "integrity": "sha1-WOJhv4uEK9I0yGyvuYKhITr/Dts=", + "dev": true, + "requires": { + "babylon": "6.18.0", + "vlq": "0.2.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -2749,12 +2783,14 @@ "dependencies": { "abbrev": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", "optional": true }, "ajv": { "version": "4.11.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", "optional": true, "requires": { "co": "4.6.0", @@ -2763,16 +2799,19 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", + "integrity": "sha1-ldNgDwdxCqDpKYxyatXs8urLq6s=", "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "optional": true, "requires": { "delegates": "1.0.0", @@ -2781,36 +2820,43 @@ }, "asn1": { "version": "0.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", "optional": true }, "assert-plus": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", "optional": true }, "asynckit": { "version": "0.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "optional": true }, "aws-sign2": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", "optional": true }, "aws4": { "version": "1.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "optional": true }, "balanced-match": { "version": "0.4.2", - "bundled": true + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" }, "bcrypt-pbkdf": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { "tweetnacl": "0.14.5" @@ -2818,21 +2864,24 @@ }, "block-stream": { "version": "0.0.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "requires": { "inherits": "2.0.3" } }, "boom": { "version": "2.10.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "requires": { "hoek": "2.16.3" } }, "brace-expansion": { "version": "1.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", + "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", "requires": { "balanced-match": "0.4.2", "concat-map": "0.0.1" @@ -2840,44 +2889,53 @@ }, "buffer-shims": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" }, "caseless": { "version": "0.12.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "optional": true }, "co": { "version": "4.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "combined-stream": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "requires": { "delayed-stream": "1.0.0" } }, "concat-map": { "version": "0.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cryptiles": { "version": "2.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "optional": true, "requires": { "boom": "2.10.1" @@ -2885,7 +2943,8 @@ }, "dashdash": { "version": "1.14.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "optional": true, "requires": { "assert-plus": "1.0.0" @@ -2893,14 +2952,16 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "optional": true } } }, "debug": { "version": "2.6.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "optional": true, "requires": { "ms": "2.0.0" @@ -2908,21 +2969,25 @@ }, "deep-extend": { "version": "0.4.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", "optional": true }, "delayed-stream": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "ecc-jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { "jsbn": "0.1.1" @@ -2930,21 +2995,25 @@ }, "extend": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "optional": true }, "extsprintf": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" }, "forever-agent": { "version": "0.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "optional": true }, "form-data": { "version": "2.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "optional": true, "requires": { "asynckit": "0.4.0", @@ -2954,11 +3023,13 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fstream": { "version": "1.0.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "requires": { "graceful-fs": "4.1.11", "inherits": "2.0.3", @@ -2968,7 +3039,8 @@ }, "fstream-ignore": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", "optional": true, "requires": { "fstream": "1.0.11", @@ -2978,7 +3050,8 @@ }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "1.1.1", @@ -2993,7 +3066,8 @@ }, "getpass": { "version": "0.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "optional": true, "requires": { "assert-plus": "1.0.0" @@ -3001,14 +3075,16 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "optional": true } } }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -3020,16 +3096,19 @@ }, "graceful-fs": { "version": "4.1.11", - "bundled": true + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "har-schema": { "version": "1.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", "optional": true }, "har-validator": { "version": "4.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", "optional": true, "requires": { "ajv": "4.11.8", @@ -3038,12 +3117,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "hawk": { "version": "3.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "optional": true, "requires": { "boom": "2.10.1", @@ -3054,11 +3135,13 @@ }, "hoek": { "version": "2.16.3", - "bundled": true + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" }, "http-signature": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "optional": true, "requires": { "assert-plus": "0.2.0", @@ -3068,7 +3151,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -3076,37 +3160,44 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "1.0.1" } }, "is-typedarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "optional": true }, "isarray": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isstream": { "version": "0.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "optional": true }, "jodid25519": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", "optional": true, "requires": { "jsbn": "0.1.1" @@ -3114,17 +3205,20 @@ }, "jsbn": { "version": "0.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, "json-schema": { "version": "0.2.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "optional": true }, "json-stable-stringify": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "optional": true, "requires": { "jsonify": "0.0.0" @@ -3132,17 +3226,20 @@ }, "json-stringify-safe": { "version": "5.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "optional": true }, "jsonify": { "version": "0.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "optional": true }, "jsprim": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", "optional": true, "requires": { "assert-plus": "1.0.0", @@ -3153,48 +3250,56 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "optional": true } } }, "mime-db": { "version": "1.27.0", - "bundled": true + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" }, "mime-types": { "version": "2.1.15", - "bundled": true, + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", "requires": { "mime-db": "1.27.0" } }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "optional": true }, "node-pre-gyp": { "version": "0.6.36", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", "optional": true, "requires": { "mkdirp": "0.5.1", @@ -3210,7 +3315,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1.1.0", @@ -3219,7 +3325,8 @@ }, "npmlog": { "version": "4.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz", + "integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==", "optional": true, "requires": { "are-we-there-yet": "1.1.4", @@ -3230,38 +3337,45 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.8.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1.0.2" } }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", "optional": true, "requires": { "os-homedir": "1.0.2", @@ -3270,30 +3384,36 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "performance-now": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", "optional": true }, "process-nextick-args": { "version": "1.0.7", - "bundled": true + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "punycode": { "version": "1.4.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "optional": true }, "qs": { "version": "6.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", "optional": true }, "rc": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", "optional": true, "requires": { "deep-extend": "0.4.2", @@ -3304,14 +3424,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.2.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", + "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", "requires": { "buffer-shims": "1.0.0", "core-util-is": "1.0.2", @@ -3324,7 +3446,8 @@ }, "request": { "version": "2.81.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", "optional": true, "requires": { "aws-sign2": "0.6.0", @@ -3353,33 +3476,39 @@ }, "rimraf": { "version": "2.6.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", "requires": { "glob": "7.1.2" } }, "safe-buffer": { "version": "5.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" }, "semver": { "version": "5.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "sntp": { "version": "1.0.9", - "bundled": true, + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "optional": true, "requires": { "hoek": "2.16.3" @@ -3387,7 +3516,8 @@ }, "sshpk": { "version": "1.13.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", + "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", "optional": true, "requires": { "asn1": "0.2.3", @@ -3403,14 +3533,16 @@ "dependencies": { "assert-plus": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "optional": true } } }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "1.1.0", "is-fullwidth-code-point": "1.0.0", @@ -3419,31 +3551,36 @@ }, "string_decoder": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", + "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", "requires": { "safe-buffer": "5.0.1" } }, "stringstream": { "version": "0.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", "optional": true }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "2.1.1" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "2.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "requires": { "block-stream": "0.0.9", "fstream": "1.0.11", @@ -3452,7 +3589,8 @@ }, "tar-pack": { "version": "3.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", + "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", "optional": true, "requires": { "debug": "2.6.8", @@ -3467,7 +3605,8 @@ }, "tough-cookie": { "version": "2.3.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", "optional": true, "requires": { "punycode": "1.4.1" @@ -3475,7 +3614,8 @@ }, "tunnel-agent": { "version": "0.6.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "optional": true, "requires": { "safe-buffer": "5.0.1" @@ -3483,26 +3623,31 @@ }, "tweetnacl": { "version": "0.14.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, "uid-number": { "version": "0.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", "optional": true }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", + "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", "optional": true }, "verror": { "version": "1.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", "optional": true, "requires": { "extsprintf": "1.0.2" @@ -3510,7 +3655,8 @@ }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "optional": true, "requires": { "string-width": "1.0.2" @@ -3518,7 +3664,8 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } }, @@ -6376,9 +6523,9 @@ "integrity": "sha1-XoYkrpNjyA+V7GRFhOzfVddPk/o=" }, "prettier-eslint": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-8.2.1.tgz", - "integrity": "sha512-z19e81masHigXh4Uz2rKL344amyeW56LWjmALnOGUZMiHDxWwYswbv2ZriA7aTKitSH909eN4+h4GqQDXeZOvA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-8.2.0.tgz", + "integrity": "sha512-OSx/0ULxWuIjeMO6KJoMVIDc7/Kj9a0wxUDzaGJTNiZXlNRq2bCl5jya8p42OQCVJzp1HnnQyGrWb+6/5+jbJQ==", "dev": true, "requires": { "common-tags": "1.4.0", @@ -6391,7 +6538,7 @@ "pretty-format": "20.0.3", "require-relative": "0.8.7", "typescript": "2.5.3", - "typescript-eslint-parser": "8.0.1" + "typescript-eslint-parser": "7.0.0" }, "dependencies": { "ansi-styles": { @@ -6418,16 +6565,6 @@ "ansi-regex": "2.1.1", "ansi-styles": "3.2.0" } - }, - "typescript-eslint-parser": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-8.0.1.tgz", - "integrity": "sha1-6MrFN9mW4Ww9uw18TVCXmeZ6/gw=", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.4.1" - } } } }, @@ -6452,7 +6589,7 @@ "lodash.memoize": "4.1.2", "loglevel-colored-level-prefix": "1.0.0", "messageformat": "1.0.2", - "prettier-eslint": "8.2.1", + "prettier-eslint": "8.2.0", "rxjs": "5.4.3", "yargs": "8.0.2" }, @@ -7848,6 +7985,24 @@ "integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==", "dev": true }, + "typescript-eslint-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-7.0.0.tgz", + "integrity": "sha1-vlfYdo43cHr4JeM56irxjXOTyrs=", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -8055,6 +8210,12 @@ } } }, + "vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", diff --git a/package.json b/package.json index e276f3566b2..e17170ee8fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webpack-cli", - "version": "1.4.0", + "version": "1.4.1", "description": "CLI for webpack & friends", "license": "MIT", "preferGlobal": true, @@ -13,13 +13,15 @@ }, "main": "./bin/webpack.js", "engines": { - "node": ">=5.0.0" + "node": ">=4.0.0" }, "scripts": { "lint": "eslint \"**/*.js\"", "format": "prettier-eslint \"bin/**/**/*.js\" --write && prettier-eslint \"lib/**/**/*.js\" --write && prettier-eslint \"test/**/**/*.js\" --write", + "format:dist": "prettier-eslint \"dist/**/**/*.js\" --write", "lint:codeOnly": "eslint \"{lib,bin,__mocks__}/**/!(__testfixtures__)/*.js\" \"{lib,bin,__mocks__}/**.js\"", "precommit": "lint-staged", + "prepublish": "flow-remove-types lib/ -d dist/ && npm run format:dist", "pretest": "npm run lint", "test": "jest --coverage" }, @@ -30,7 +32,10 @@ ] }, "jest": { - "testEnvironment": "node" + "testEnvironment": "node", + "modulePathIgnorePatterns": [ + "dist" + ] }, "dependencies": { "babel-code-frame": "^6.22.0", @@ -71,8 +76,12 @@ "babel-eslint": "^7.2.3", "babel-jest": "^20.0.3", "babel-polyfill": "^6.23.0", + "babel-preset-flow": "^6.23.0", "eslint": "^4.2.0", + "eslint-plugin-flowtype": "^2.35.1", "eslint-plugin-node": "^5.1.0", + "flow-bin": "^0.49.1", + "flow-remove-types": "^1.2.1", "husky": "^0.14.3", "jest": "^21.1.0", "jest-cli": "^21.1.0",