UNPKG

ember-cli

Version:

Command line tool for developing ambitious ember.js apps

234 lines (210 loc) 7.3 kB
var fs = require('fs') var path = require('path') var crypto = require('crypto') var mkdirp = require('mkdirp') var glob = require('glob') var isWindows = process.platform === 'win32' var pathSep = path.sep var keysForTreeWarningPrinted = false exports.hashTree = hashTree // This function is used by the watcher. It makes the following guarantees: // // (1) It never throws an exception. // // (2) It does not miss changes. In other words, if after this function returns, // any part of the directory hierarchy changes, a subsequent call must // return a different hash. // // (1) and (2) hold even in the face of a constantly-changing file system. function hashTree (fullPath) { return hashStrings(keysForTree(fullPath)) } function keysForTree (fullPath, initialRelativePath) { var relativePath = initialRelativePath || '.' var stats var statKeys try { stats = fs.statSync(fullPath) } catch (err) { if (!keysForTreeWarningPrinted) { console.warn('Warning: failed to stat ' + fullPath) keysForTreeWarningPrinted = true } // fullPath has probably ceased to exist. Leave `stats` undefined and // proceed hashing. } var childKeys = [] if (stats) { statKeys = ['stats', stats.mode] } else { statKeys = ['stat failed'] } if (stats && stats.isDirectory()) { var fileIdentity = stats.dev + '\x00' + stats.ino 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]), path.join(relativePath, entries[i]) ) childKeys = childKeys.concat(keys) } } } else if (stats && stats.isFile()) { statKeys.push(stats.mtime.getTime()) statKeys.push(stats.size) } return ['path', relativePath] .concat(statKeys) .concat(childKeys) } exports.hashStats = hashStats function hashStats (stats, path) { // Both stats and path can be null var keys = [] if (stats != null) { keys.push(stats.mode, stats.size, stats.mtime.getTime()) } if (path != null) { keys.push(path) } return hashStrings(keys) } exports.hashStrings = hashStrings function hashStrings (strings) { var joinedStrings = strings.join('\x00') return crypto.createHash('md5').update(joinedStrings).digest('hex') } // If src is a file, dest is a file name. If src is a directory, dest is the // directory that the contents of src will be copied into. // // This function refuses to overwrite files, but accepts if directories exist // already. // // This function dereferences symlinks. // // Note that unlike cp(1), we do not special-case if dest is an existing // directory, because relying on things to exist when we're in the middle of // assembling a new tree is too brittle. // // This function is deprecated in favor of // https://github.com/broccolijs/node-copy-dereference // // copy-dereference differs from copyRecursivelySync in that it won't call // mkdirp to create the target directory (or the parent directory of the // target file), which makes it stricter: (1) It's not OK for the target // directory to exist already, and (2) missing parent directories will not // automatically be created. exports.copyRecursivelySync = copyRecursivelySync function copyRecursivelySync (src, dest, _mkdirp) { if (_mkdirp == null) _mkdirp = true // Note: We could try readdir'ing and catching ENOTDIR exceptions, but that // is 3x slower than stat'ing in the common case that we have a file. var srcStats = fs.statSync(src) if (srcStats.isDirectory()) { mkdirp.sync(dest) var entries = fs.readdirSync(src).sort() for (var i = 0; i < entries.length; i++) { // Set _mkdirp to false when recursing to avoid extra mkdirp calls. copyRecursivelySync(src + '/' + entries[i], dest + '/' + entries[i], false) } } else { if (_mkdirp) { mkdirp.sync(path.dirname(dest)) } copyPreserveSync(src, dest, srcStats) } } // This function is deprecated in favor of // https://github.com/broccolijs/node-copy-dereference // // srcStats is optional; use it as an optimization to avoid double stats // This function refuses to overwrite files. exports.copyPreserveSync = copyPreserveSync function copyPreserveSync (src, dest, srcStats) { if (srcStats == null) srcStats = fs.statSync(src) if (srcStats.isFile()) { var content = fs.readFileSync(src) fs.writeFileSync(dest, content, { flag: 'wx' }) fs.utimesSync(dest, srcStats.atime, srcStats.mtime) } else { throw new Error('Unexpected file type for ' + src) } } exports.linkRecursivelySync = linkRecursivelySync function linkRecursivelySync () { throw new Error('linkRecursivelySync has been removed; use copyRecursivelySync instead (note: it does not overwrite)') } exports.linkAndOverwrite = linkAndOverwrite function linkAndOverwrite () { throw new Error('linkAndOverwrite has been removed; use copyPreserveSync instead (note: it does not overwrite)') } exports.assertAbsolutePaths = assertAbsolutePaths function assertAbsolutePaths (paths) { for (var i = 0; i < paths.length; i++) { if (paths[i][0] !== '/') { throw new Error('Path must be absolute: "' + paths[i] + '"') } } } // Multi-glob with reasonable defaults, so APIs all behave the same exports.multiGlob = multiGlob function multiGlob (globs, globOptions) { if (!Array.isArray(globs)) { throw new TypeError("multiGlob's first argument must be an array"); } var options = { nomount: true, strict: true } for (var key in globOptions) { if (globOptions.hasOwnProperty(key)) { options[key] = globOptions[key] } } var pathSet = {} var paths = [] for (var i = 0; i < globs.length; i++) { if (options.nomount && globs[i][0] === '/') { throw new Error('Absolute paths not allowed (`nomount` is enabled): ' + globs[i]) } var matches = glob.sync(globs[i], options) if (matches.length === 0) { throw new Error('Path or pattern "' + globs[i] + '" did not match any files') } for (var j = 0; j < matches.length; j++) { if (!pathSet[matches[j]]) { pathSet[matches[j]] = true paths.push(matches[j]) } } } return paths } // This function is deprecated in favor of // https://github.com/broccolijs/node-symlink-or-copy exports.symlinkOrCopyPreserveSync = symlinkOrCopyPreserveSync function symlinkOrCopyPreserveSync (sourcePath, destPath) { if (isWindows) { copyRecursivelySync(sourcePath, destPath) } else { if (fs.lstatSync(sourcePath).isSymbolicLink()) { // When we encounter symlinks, follow them. This prevents indirection // from growing out of control. Note: At the moment `realpath` on Node // is 70x slower than native: https://github.com/joyent/node/issues/7902 sourcePath = fs.realpathSync(sourcePath) } else if (sourcePath[0] !== pathSep) { sourcePath = process.cwd() + pathSep + sourcePath } fs.symlinkSync(sourcePath, destPath) } }