git-off

git off handles large files in git repos
git clone https://noulin.net/git/git-off.git
Log | Files | Refs | README

change-creator.js (6035B)


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