change-creator.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. var fs = require('fs');
  2. var path = require('path');
  3. var crypto = require('crypto');
  4. /**
  5. * Configuration Options:
  6. * - write file location
  7. * - read file location
  8. */
  9. function checkProperty(obj, prop) {
  10. return Object.prototype.hasOwnProperty.call(obj, prop);
  11. }
  12. /**
  13. * Generates a 'random' hex value.
  14. * More 'random' than Math.random without depending on a GUID module.
  15. */
  16. function generateRandomIdentifier() {
  17. return crypto.randomBytes(4).toString('hex');
  18. }
  19. /**
  20. * Escapes illegal characters from filename
  21. * Ref: https://github.com/aws/aws-sdk-js/issues/3691
  22. */
  23. function sanitizeFileName(filename) {
  24. return filename.replace(/[^a-zA-Z0-9\\.]/g, '-');
  25. }
  26. var CHANGES_DIR = path.join(process.cwd(), '.changes');
  27. /**
  28. * A map of valid change types.
  29. * Can be referenced outside of this module.
  30. */
  31. var VALID_TYPES = Object.create(null);
  32. VALID_TYPES['bugfix'] = true;
  33. VALID_TYPES['feature'] = true;
  34. /**
  35. * Handles creating a change log entry JSON file.
  36. */
  37. function ChangeCreator(config) {
  38. this._config = config || {};
  39. this._type = '';
  40. this._category = '';
  41. this._description = '';
  42. }
  43. ChangeCreator.prototype = {
  44. getChangeType: function getChangeType() {
  45. return this._type;
  46. },
  47. setChangeType: function setChangeType(type) {
  48. this._type = type;
  49. },
  50. getChangeCategory: function getChangeCategory() {
  51. return this._category;
  52. },
  53. setChangeCategory: function setChangeCategory(category) {
  54. this._category = category;
  55. },
  56. getChangeDescription: function getChangeDescription() {
  57. return this._description;
  58. },
  59. setChangeDescription: function setChangeDescription(description) {
  60. this._description = description;
  61. },
  62. /**
  63. * Validates the entire change entry.
  64. */
  65. validateChange: function validateChange() {
  66. var type = this.getChangeType();
  67. var category = this.getChangeCategory();
  68. var description = this.getChangeDescription();
  69. var missingFields = [];
  70. this.validateChangeType(type);
  71. this.validateChangeCategory(category);
  72. this.validateChangeDescription(description);
  73. return this;
  74. },
  75. /**
  76. * Validates a change entry type.
  77. */
  78. validateChangeType: function validateChangeType(type) {
  79. var type = type || this._type;
  80. if (!type) {
  81. throw new Error('ValidationError: Missing `type` field.');
  82. }
  83. if (VALID_TYPES[type]) {
  84. return this;
  85. }
  86. var validTypes = Object.keys(VALID_TYPES).join(',');
  87. throw new Error('ValidationError: `type` set as "' + type + '" but must be one of [' + validTypes + '].');
  88. },
  89. /**
  90. * Validates a change entry category.
  91. */
  92. validateChangeCategory: function validateChangeCategory(category) {
  93. var category = category || this._category;
  94. if (!category) {
  95. throw new Error('ValidationError: Missing `category` field.');
  96. }
  97. return this;
  98. },
  99. /**
  100. * Validates a change entry description.
  101. */
  102. validateChangeDescription: function validateChangeDescription(description) {
  103. var description = description || this._description;
  104. if (!description) {
  105. throw new Error('ValidationError: Missing `description` field.');
  106. }
  107. return this;
  108. },
  109. /**
  110. * Creates the output directory if it doesn't exist.
  111. */
  112. createOutputDirectory: function createOutputDirectory(outPath) {
  113. var pathObj = path.parse(outPath);
  114. var sep = path.sep;
  115. var directoryStructure = pathObj.dir.split(sep) || [];
  116. for (var i = 0; i < directoryStructure.length; i++) {
  117. var pathToCheck = directoryStructure.slice(0, i + 1).join(sep);
  118. if (!pathToCheck) {
  119. continue;
  120. }
  121. try {
  122. var stats = fs.statSync(pathToCheck);
  123. } catch (err) {
  124. if (err.code === 'ENOENT') {
  125. // Directory doesn't exist, so create it
  126. fs.mkdirSync(pathToCheck);
  127. } else {
  128. throw err;
  129. }
  130. }
  131. }
  132. return this;
  133. },
  134. /**
  135. * Returns a path to the future change entry file.
  136. */
  137. determineWriteLocation: function determineWriteLocation() {
  138. /* Order for determining write location:
  139. 1) Check configuration for `outFile` location.
  140. 2) Check configuration for `inFile` location.
  141. 3) Create a new file using default location.
  142. */
  143. var config = this._config || {};
  144. if (checkProperty(config, 'outFile') && config['outFile']) {
  145. return config['outFile'];
  146. }
  147. if (checkProperty(config, 'inFile') && config['inFile']) {
  148. return config['inFile'];
  149. }
  150. // Determine default location
  151. var newFileName = sanitizeFileName(this._type) + '-' + sanitizeFileName(this._category)
  152. + '-' + generateRandomIdentifier() + '.json';
  153. return path.join(process.cwd(), '.changes', 'next-release', newFileName);
  154. },
  155. /**
  156. * Writes a change entry as a JSON file.
  157. */
  158. writeChanges: function writeChanges(callback) {
  159. var hasCallback = typeof callback === 'function';
  160. var fileLocation = this.determineWriteLocation();
  161. try {
  162. // Will throw an error if the change is not valid
  163. this.validateChange().createOutputDirectory(fileLocation);
  164. var change = {
  165. type: this.getChangeType(),
  166. category: this.getChangeCategory(),
  167. description: this.getChangeDescription()
  168. }
  169. fs.writeFileSync(fileLocation, JSON.stringify(change, null, 2));
  170. var data = {
  171. file: fileLocation
  172. };
  173. if (hasCallback) {
  174. return callback(null, data);
  175. } else {
  176. return data;
  177. }
  178. } catch (err) {
  179. if (hasCallback) {
  180. return callback(err, null);
  181. } else {
  182. throw err;
  183. }
  184. }
  185. }
  186. }
  187. module.exports = {
  188. ChangeCreator: ChangeCreator,
  189. VALID_TYPES: VALID_TYPES
  190. };