/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ 'use strict'; const fs = require('fs'); const path = require('path'); // Binary files, don't process these (avoid decoding as utf8) const binaryExtensions = ['.png', '.jar']; /** * Copy a file to given destination, replacing parts of its contents. * @param srcPath Path to a file to be copied. * @param destPath Destination path. * @param replacements: e.g. {'TextToBeReplaced': 'Replacement'} * @param contentChangedCallback * Used when upgrading projects. Based on if file contents would change * when being replaced, allows the caller to specify whether the file * should be replaced or not. * If null, files will be overwritten. * Function(path, 'identical' | 'changed' | 'new') => 'keep' | 'overwrite' */ function copyAndReplace(srcPath, destPath, replacements, contentChangedCallback) { if (fs.lstatSync(srcPath).isDirectory()) { if (!fs.existsSync(destPath)) { fs.mkdirSync(destPath); } // Not recursive return; } const extension = path.extname(srcPath); if (binaryExtensions.indexOf(extension) !== -1) { // Binary file let shouldOverwrite = 'overwrite'; if (contentChangedCallback) { const newContentBuffer = fs.readFileSync(srcPath); let contentChanged = 'identical'; try { const origContentBuffer = fs.readFileSync(destPath); if (Buffer.compare(origContentBuffer, newContentBuffer) !== 0) { contentChanged = 'changed'; } } catch (err) { if (err.code === 'ENOENT') { contentChanged = 'new'; } else { throw err; } } shouldOverwrite = contentChangedCallback(destPath, contentChanged); } if (shouldOverwrite === 'overwrite') { copyBinaryFile(srcPath, destPath, (err) => { if (err) { throw err; } }); } } else { // Text file const srcPermissions = fs.statSync(srcPath).mode; let content = fs.readFileSync(srcPath, 'utf8'); Object.keys(replacements).forEach(regex => content = content.replace(new RegExp(regex, 'g'), replacements[regex]) ); let shouldOverwrite = 'overwrite'; if (contentChangedCallback) { // Check if contents changed and ask to overwrite let contentChanged = 'identical'; try { const origContent = fs.readFileSync(destPath, 'utf8'); if (content !== origContent) { //console.log('Content changed: ' + destPath); contentChanged = 'changed'; } } catch (err) { if (err.code === 'ENOENT') { contentChanged = 'new'; } else { throw err; } } shouldOverwrite = contentChangedCallback(destPath, contentChanged); } if (shouldOverwrite === 'overwrite') { fs.writeFileSync(destPath, content, { encoding: 'utf8', mode: srcPermissions, }); } } } /** * Same as 'cp' on Unix. Don't do any replacements. */ function copyBinaryFile(srcPath, destPath, cb) { let cbCalled = false; const srcPermissions = fs.statSync(srcPath).mode; const readStream = fs.createReadStream(srcPath); readStream.on('error', function(err) { done(err); }); const writeStream = fs.createWriteStream(destPath, { mode: srcPermissions }); writeStream.on('error', function(err) { done(err); }); writeStream.on('close', function(ex) { done(); }); readStream.pipe(writeStream); function done(err) { if (!cbCalled) { cb(err); cbCalled = true; } } } module.exports = copyAndReplace;