UNPKG

ember-cli

Version:

Command line tool for developing ambitious ember.js apps

194 lines (159 loc) 5.39 kB
var fs = require('fs'); var path = require('path'); var RSVP = require('rsvp'); var mkdirp = require('mkdirp') var walkSync = require('walk-sync'); var quickTemp = require('quick-temp') var Writer = require('broccoli-writer'); var helpers = require('broccoli-kitchen-sink-helpers'); var canLink = testCanLink(); CachingWriter.prototype = Object.create(Writer.prototype); CachingWriter.prototype.constructor = CachingWriter; function CachingWriter (inputTree, options) { if (!(this instanceof CachingWriter)) return new CachingWriter(inputTree, options); this.inputTree = inputTree; options = options || {}; for (var key in options) { if (options.hasOwnProperty(key)) { this[key] = options[key]; } } }; CachingWriter.prototype.getCacheDir = function () { return quickTemp.makeOrReuse(this, 'tmpCacheDir'); }; CachingWriter.prototype.getCleanCacheDir = function () { return quickTemp.makeOrRemake(this, 'tmpCacheDir'); }; CachingWriter.prototype.write = function (readTree, destDir) { var self = this; return readTree(this.inputTree).then(function (srcDir) { var inputTreeKeys = keysForTree(srcDir); var inputTreeHash = helpers.hashStrings(inputTreeKeys); return RSVP.resolve() .then(function() { var updateCacheResult; if (inputTreeHash !== self._cacheHash) { updateCacheResult = self.updateCache(srcDir, self.getCleanCacheDir()); self._cacheHash = inputTreeHash; self._cacheTreeKeys = inputTreeKeys; } return updateCacheResult; }) .finally(function() { linkFromCache(self.getCacheDir(), destDir); }); }); }; CachingWriter.prototype.cleanup = function () { quickTemp.remove(this, 'tmpCacheDir'); Writer.prototype.cleanup.call(this); }; CachingWriter.prototype.updateCache = function (srcDir, destDir) { throw new Error('You must implement updateCache.'); }; module.exports = CachingWriter; function linkFromCache(srcDir, destDir) { var files = walkSync(srcDir); var length = files.length; var file; for (var i = 0; i < length; i++) { file = files[i]; var srcFile = path.join(srcDir, file); var stats = fs.statSync(srcFile); if (stats.isDirectory()) { continue; } if (!stats.isFile()) { throw new Error('Can not link non-file.'); } destFile = path.join(destDir, file); mkdirp.sync(path.dirname(destFile)); if (canLink) { fs.linkSync(srcFile, destFile); } else { fs.writeFileSync(destFile, fs.readFileSync(srcFile)); } } } function keysForTree (fullPath, options) { options = options || {} var _stack = options._stack; var _followSymlink = options._followSymlink; var relativePath = options.relativePath || '.'; var stats; var statKeys; try { if (_followSymlink) { stats = fs.statSync(fullPath); } else { stats = fs.lstatSync(fullPath); } } catch (err) { console.warn('Warning: failed to stat ' + fullPath); // fullPath has probably ceased to exist. Leave `stats` undefined and // proceed hashing. } var childKeys = []; if (stats) { statKeys = ['stats', stats.mode, stats.size]; } else { statKeys = ['stat failed']; } if (stats && stats.isDirectory()) { var fileIdentity = stats.dev + '\x00' + stats.ino; if (_stack != null && _stack.indexOf(fileIdentity) !== -1) { console.warn('Symlink directory loop detected at ' + fullPath + ' (note: loop detection may have false positives on Windows)'); } else { if (_stack != null) _stack = _stack.concat([fileIdentity]); var entries; try { entries = fs.readdirSync(fullPath).sort(); } catch (err) { console.warn('Warning: Failed to read directory ' + fullPath); console.warn(err.stack); childKeys = ['readdir failed']; // That's all there is to say about this directory. } if (entries != null) { for (var i = 0; i < entries.length; i++) { var keys = keysForTree(path.join(fullPath, entries[i]), { _stack: _stack, relativePath: path.join(relativePath, entries[i]) }); childKeys = childKeys.concat(keys); } } } } else if (stats && stats.isSymbolicLink()) { if (_stack == null) { // From here on in the traversal, we need to guard against symlink // directory loops. _stack is kept null in the absence of symlinks to we // don't have to deal with Windows for now, as long as it doesn't use // symlinks. _stack = []; } childKeys = keysForTree(fullPath, {_stack: _stack, relativePath: relativePath, _followSymlink: true}); // follow symlink statKeys.push(stats.mtime.getTime()); } else if (stats && stats.isFile()) { statKeys.push(stats.mtime.getTime()); } // Perhaps we should not use basename to infer the file name return ['path', relativePath] .concat(statKeys) .concat(childKeys); } function testCanLink () { var canLinkSrc = path.join(__dirname, "canLinkSrc.tmp"); var canLinkDest = path.join(__dirname, "canLinkDest.tmp"); try { fs.writeFileSync(canLinkSrc); } catch (e) { return false; } try { fs.linkSync(canLinkSrc, canLinkDest); } catch (e) { fs.unlinkSync(canLinkSrc); return false; } fs.unlinkSync(canLinkDest); return true; }