migrate.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. "use strict";
  2. const fs = require("fs");
  3. const path = require("path");
  4. const chalk = require("chalk");
  5. const diff = require("diff");
  6. const inquirer = require("inquirer");
  7. const PLazy = require("p-lazy");
  8. const Listr = require("listr");
  9. const { validate } = require("webpack");
  10. const { WebpackOptionsValidationError } = require("webpack");
  11. const runPrettier = require("../utils/run-prettier");
  12. /**
  13. *
  14. * Runs migration on a given configuration using AST's and promises
  15. * to sequentially transform a configuration file.
  16. *
  17. * @param {Array} args - Migrate arguments such as input and
  18. * output path
  19. * @returns {Function} Runs the migration using the 'runMigrate'
  20. * function.
  21. */
  22. module.exports = function migrate(...args) {
  23. const filePaths = args.slice(3);
  24. if (!filePaths.length) {
  25. const errMsg = "\n ✖ Please specify a path to your webpack config \n ";
  26. console.error(chalk.red(errMsg));
  27. return;
  28. }
  29. const currentConfigPath = path.resolve(process.cwd(), filePaths[0]);
  30. let outputConfigPath;
  31. if (!filePaths[1]) {
  32. return inquirer
  33. .prompt([
  34. {
  35. type: "confirm",
  36. name: "confirmPath",
  37. message:
  38. "Migration output path not specified. " +
  39. "Do you want to use your existing webpack " +
  40. "configuration?",
  41. default: "Y"
  42. }
  43. ])
  44. .then(ans => {
  45. if (!ans["confirmPath"]) {
  46. console.error(chalk.red("✖ ︎Migration aborted due no output path"));
  47. return;
  48. }
  49. outputConfigPath = path.resolve(process.cwd(), filePaths[0]);
  50. return runMigration(currentConfigPath, outputConfigPath);
  51. })
  52. .catch(err => {
  53. console.error(err);
  54. });
  55. }
  56. outputConfigPath = path.resolve(process.cwd(), filePaths[1]);
  57. return runMigration(currentConfigPath, outputConfigPath);
  58. };
  59. /**
  60. *
  61. * Runs migration on a given configuration using AST's and promises
  62. * to sequentially transform a configuration file.
  63. *
  64. * @param {String} currentConfigPath - input path for config
  65. * @param {String} outputConfigPath - output path for config
  66. * @returns {Promise} Runs the migration using a promise that
  67. * will throw any errors during each transform or output if the
  68. * user decides to abort the migration
  69. */
  70. function runMigration(currentConfigPath, outputConfigPath) {
  71. const recastOptions = {
  72. quote: "single"
  73. };
  74. const tasks = new Listr([
  75. {
  76. title: "Reading webpack config",
  77. task: ctx =>
  78. new PLazy((resolve, reject) => {
  79. fs.readFile(currentConfigPath, "utf8", (err, content) => {
  80. if (err) {
  81. reject(err);
  82. }
  83. try {
  84. const jscodeshift = require("jscodeshift");
  85. ctx.source = content;
  86. ctx.ast = jscodeshift(content);
  87. resolve();
  88. } catch (err) {
  89. reject("Error generating AST", err);
  90. }
  91. });
  92. })
  93. },
  94. {
  95. title: "Migrating config to newest version",
  96. task: ctx => {
  97. const transformations = require("../migrate").transformations;
  98. return new Listr(
  99. Object.keys(transformations).map(key => {
  100. const transform = transformations[key];
  101. return {
  102. title: key,
  103. task: _ => transform(ctx.ast, ctx.source)
  104. };
  105. })
  106. );
  107. }
  108. }
  109. ]);
  110. tasks
  111. .run()
  112. .then(ctx => {
  113. const result = ctx.ast.toSource(recastOptions);
  114. const diffOutput = diff.diffLines(ctx.source, result);
  115. diffOutput.forEach(diffLine => {
  116. if (diffLine.added) {
  117. process.stdout.write(chalk.green(`+ ${diffLine.value}`));
  118. } else if (diffLine.removed) {
  119. process.stdout.write(chalk.red(`- ${diffLine.value}`));
  120. }
  121. });
  122. return inquirer
  123. .prompt([
  124. {
  125. type: "confirm",
  126. name: "confirmMigration",
  127. message: "Are you sure these changes are fine?",
  128. default: "Y"
  129. }
  130. ])
  131. .then(answers => {
  132. if (answers["confirmMigration"]) {
  133. return inquirer.prompt([
  134. {
  135. type: "confirm",
  136. name: "confirmValidation",
  137. message:
  138. "Do you want to validate your configuration? " +
  139. "(If you're using webpack merge, validation isn't useful)",
  140. default: "Y"
  141. }
  142. ]);
  143. } else {
  144. console.log(chalk.red("✖ Migration aborted"));
  145. }
  146. })
  147. .then(answer => {
  148. if (!answer) return;
  149. runPrettier(outputConfigPath, result, err => {
  150. if (err) {
  151. throw err;
  152. }
  153. });
  154. if (answer["confirmValidation"]) {
  155. const webpackOptionsValidationErrors = validate(
  156. require(outputConfigPath)
  157. );
  158. if (webpackOptionsValidationErrors.length) {
  159. console.log(
  160. chalk.red(
  161. "\n✖ Your configuration validation wasn't successful \n"
  162. )
  163. );
  164. console.error(
  165. new WebpackOptionsValidationError(
  166. webpackOptionsValidationErrors
  167. ).message
  168. );
  169. }
  170. }
  171. console.log(
  172. chalk.green(
  173. `\n✔︎ New webpack config file is at ${outputConfigPath}.`
  174. )
  175. );
  176. console.log(
  177. chalk.green(
  178. "✔︎ Heads up! Updating to the latest version could contain breaking changes."
  179. )
  180. );
  181. console.log(
  182. chalk.green(
  183. "✔︎ Plugin and loader dependencies may need to be updated."
  184. )
  185. );
  186. });
  187. })
  188. .catch(err => {
  189. const errMsg = "\n ✖ ︎Migration aborted due to some errors: \n";
  190. console.error(chalk.red(errMsg));
  191. console.error(err);
  192. process.exitCode = 1;
  193. });
  194. }