diff --git a/packages/cli/package.json b/packages/cli/package.json index 7a4b466a6..4feb0411f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,7 +39,6 @@ "glob": "^7.1.1", "graceful-fs": "^4.1.3", "inquirer": "^3.0.6", - "istextorbinary": "^2.5.1", "joi": "^14.3.1", "lodash": "^4.17.5", "metro": "^0.53.1", diff --git a/packages/cli/src/commands/init/__tests__/editTemplate.test.js b/packages/cli/src/commands/init/__tests__/editTemplate.test.js index f664d2557..f59c52abc 100644 --- a/packages/cli/src/commands/init/__tests__/editTemplate.test.js +++ b/packages/cli/src/commands/init/__tests__/editTemplate.test.js @@ -16,20 +16,28 @@ const FIXTURE_DIR = path.resolve( const PLACEHOLDER_NAME = 'PlaceholderName'; const PROJECT_NAME = 'ProjectName'; -function createTestEnv() { +async function createTestEnv() { const TEST_DIR = `rncli-should-edit-template-${Date.now()}`; const tmpDir = os.tmpdir(); const testPath = path.resolve(tmpDir, TEST_DIR); fs.mkdirSync(testPath); - copyFiles(FIXTURE_DIR, testPath); + await copyFiles(FIXTURE_DIR, testPath); return testPath; } -test('should edit template', () => { - const testPath = createTestEnv(); +let testPath; + +beforeEach(async () => { + testPath = await createTestEnv(); +}); +afterEach(() => { + fs.removeSync(testPath); +}); + +test('should edit template', () => { jest.spyOn(process, 'cwd').mockImplementation(() => testPath); changePlaceholderInTemplate(PROJECT_NAME, PLACEHOLDER_NAME); @@ -74,6 +82,4 @@ test('should edit template', () => { expect( snapshotDiff(fixtureTree, transformedTree, {contextLines: 1}), ).toMatchSnapshot(); - - fs.removeSync(testPath); }); diff --git a/packages/cli/src/commands/init/__tests__/template.test.js b/packages/cli/src/commands/init/__tests__/template.test.js index 3c5542202..3af48b748 100644 --- a/packages/cli/src/commands/init/__tests__/template.test.js +++ b/packages/cli/src/commands/init/__tests__/template.test.js @@ -56,7 +56,7 @@ test('getTemplateConfig', () => { ); }); -test('copyTemplate', () => { +test('copyTemplate', async () => { const TEMPLATE_DIR = 'some/dir'; const CWD = '.'; @@ -64,7 +64,7 @@ test('copyTemplate', () => { jest.spyOn(copyFiles, 'default').mockImplementationOnce(() => {}); jest.spyOn(process, 'cwd').mockImplementationOnce(() => CWD); - copyTemplate(TEMPLATE_NAME, TEMPLATE_DIR, TEMPLATE_SOURCE_DIR); + await copyTemplate(TEMPLATE_NAME, TEMPLATE_DIR, TEMPLATE_SOURCE_DIR); expect(path.resolve).toHaveBeenCalledWith( TEMPLATE_SOURCE_DIR, diff --git a/packages/cli/src/commands/init/editTemplate.js b/packages/cli/src/commands/init/editTemplate.js index 10c0e9df3..a72ca07f3 100644 --- a/packages/cli/src/commands/init/editTemplate.js +++ b/packages/cli/src/commands/init/editTemplate.js @@ -1,5 +1,5 @@ // @flow -import fs from 'fs-extra'; +import fs from 'fs'; import path from 'path'; import walk from '../../tools/walk'; import {logger} from '@react-native-community/cli-tools'; @@ -10,16 +10,17 @@ function replaceNameInUTF8File( templateName: string, ) { logger.debug(`Replacing in ${filePath}`); - - const content = fs - .readFileSync(filePath, 'utf8') + const fileContent = fs.readFileSync(filePath, 'utf8'); + const replacedFileContent = fileContent .replace(new RegExp(templateName, 'g'), projectName) .replace( new RegExp(templateName.toLowerCase(), 'g'), projectName.toLowerCase(), ); - fs.writeFileSync(filePath, content, 'utf8'); + if (fileContent !== replacedFileContent) { + fs.writeFileSync(filePath, replacedFileContent, 'utf8'); + } } function renameFile(filePath: string, oldName: string, newName: string) { @@ -30,7 +31,7 @@ function renameFile(filePath: string, oldName: string, newName: string) { logger.debug(`Renaming ${filePath} -> file:${newFileName}`); - fs.moveSync(filePath, newFileName); + fs.renameSync(filePath, newFileName); } function shouldRenameFile(filePath: string, nameToReplace: string) { diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index 80fe8079d..c3b306b71 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -63,7 +63,7 @@ async function createFromExternalTemplate( name = adjustNameIfUrl(name, templateSourceDir); const templateConfig = getTemplateConfig(name, templateSourceDir); - copyTemplate(name, templateConfig.templateDir, templateSourceDir); + await copyTemplate(name, templateConfig.templateDir, templateSourceDir); loader.succeed(); loader.start('Preparing template'); @@ -121,7 +121,11 @@ async function createFromReactNativeTemplate( loader.start('Copying template'); const templateConfig = getTemplateConfig(TEMPLATE_NAME, templateSourceDir); - copyTemplate(TEMPLATE_NAME, templateConfig.templateDir, templateSourceDir); + await copyTemplate( + TEMPLATE_NAME, + templateConfig.templateDir, + templateSourceDir, + ); loader.succeed(); loader.start('Processing template'); diff --git a/packages/cli/src/commands/init/template.js b/packages/cli/src/commands/init/template.js index b797b647e..8e2f9dede 100644 --- a/packages/cli/src/commands/init/template.js +++ b/packages/cli/src/commands/init/template.js @@ -40,7 +40,7 @@ export function getTemplateConfig( return require(configFilePath); } -export function copyTemplate( +export async function copyTemplate( templateName: string, templateDir: string, templateSourceDir: string, @@ -54,7 +54,7 @@ export function copyTemplate( logger.debug(`Copying template from ${templatePath}`); - copyFiles(templatePath, process.cwd()); + await copyFiles(templatePath, process.cwd()); } export function executePostInitScript( diff --git a/packages/cli/src/tools/copyAndReplace.js b/packages/cli/src/tools/copyAndReplace.js index 09deb7ad9..1feb0e6f2 100644 --- a/packages/cli/src/tools/copyAndReplace.js +++ b/packages/cli/src/tools/copyAndReplace.js @@ -8,7 +8,10 @@ */ import fs from 'fs'; -import {isBinarySync} from 'istextorbinary'; +import path from 'path'; + +// Binary files, don't process these (avoid decoding as utf8) +const binaryExtensions = ['.png', '.jar', '.keystore']; /** * Copy a file to given destination, replacing parts of its contents. @@ -36,7 +39,8 @@ function copyAndReplace( return; } - if (isBinarySync(srcPath)) { + const extension = path.extname(srcPath); + if (binaryExtensions.indexOf(extension) !== -1) { // Binary file let shouldOverwrite = 'overwrite'; if (contentChangedCallback) { diff --git a/packages/cli/src/tools/copyFiles.js b/packages/cli/src/tools/copyFiles.js index c94581f78..795f6862c 100644 --- a/packages/cli/src/tools/copyFiles.js +++ b/packages/cli/src/tools/copyFiles.js @@ -7,22 +7,75 @@ * @flow */ +import fs from 'fs'; import path from 'path'; -import copyAndReplace from './copyAndReplace'; import walk from './walk'; /** * Copy files (binary included) recursively. */ -function copyFiles(srcPath: string, destPath: string) { - walk(srcPath).forEach(absoluteSrcFilePath => { - const relativeFilePath = path.relative(srcPath, absoluteSrcFilePath); - copyAndReplace( - absoluteSrcFilePath, - path.resolve(destPath, relativeFilePath), - {}, // no replacements - ); +async function copyFiles(srcPath: string, destPath: string) { + return Promise.all( + walk(srcPath).map(async absoluteSrcFilePath => { + const relativeFilePath = path.relative(srcPath, absoluteSrcFilePath); + await copyFile( + absoluteSrcFilePath, + path.resolve(destPath, relativeFilePath), + ); + }), + ); +} + +/** + * Copy a file to given destination. + */ +function copyFile(srcPath: string, destPath: string) { + if (fs.lstatSync(srcPath).isDirectory()) { + if (!fs.existsSync(destPath)) { + fs.mkdirSync(destPath); + } + // Not recursive + return; + } + + return new Promise((resolve, reject) => { + copyBinaryFile(srcPath, destPath, err => { + if (err) { + reject(err); + } + resolve(destPath); + }); + }); +} + +/** + * Same as 'cp' on Unix. Don't do any replacements. + */ +function copyBinaryFile(srcPath, destPath, cb) { + let cbCalled = false; + // const {mode} = fs.statSync(srcPath); + const readStream = fs.createReadStream(srcPath); + const writeStream = fs.createWriteStream(destPath); + readStream.on('error', err => { + done(err); + }); + writeStream.on('error', err => { + done(err); + }); + readStream.on('close', () => { + done(); + // We may revisit setting mode to original later, however this fn is used + // before "replace placeholder in template" step, which expects files to be + // writable. + // fs.chmodSync(destPath, mode); }); + readStream.pipe(writeStream); + function done(err) { + if (!cbCalled) { + cb(err); + cbCalled = true; + } + } } export default copyFiles; diff --git a/yarn.lock b/yarn.lock index 820d64cb9..c3750962c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2342,11 +2342,6 @@ binary@~0.3.0: buffers "~0.1.1" chainsaw "~0.1.0" -binaryextensions@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.2.tgz#c83c3d74233ba7674e4f313cb2a2b70f54e94b7c" - integrity sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg== - block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -3377,14 +3372,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -editions@^2.1.2, editions@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/editions/-/editions-2.1.3.tgz#727ccf3ec2c7b12dcc652c71000f16c4824d6f7d" - integrity sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw== - dependencies: - errlop "^1.1.1" - semver "^5.6.0" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3420,13 +3407,6 @@ err-code@^1.0.0: resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= -errlop@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.1.1.tgz#d9ae4c76c3e64956c5d79e6e035d6343bfd62250" - integrity sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw== - dependencies: - editions "^2.1.2" - error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -5078,15 +5058,6 @@ istanbul-reports@^2.1.1: dependencies: handlebars "^4.1.0" -istextorbinary@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/istextorbinary/-/istextorbinary-2.5.1.tgz#14a33824cf6b9d5d7743eac1be2bd2c310d0ccbd" - integrity sha512-pv/JNPWnfpwGjPx7JrtWTwsWsxkrK3fNzcEVnt92YKEIErps4Fsk49+qzCe9iQF2hjqK8Naqf8P9kzoeCuQI1g== - dependencies: - binaryextensions "^2.1.2" - editions "^2.1.3" - textextensions "^2.4.0" - isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" @@ -8587,11 +8558,6 @@ text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -textextensions@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-2.4.0.tgz#6a143a985464384cc2cff11aea448cd5b018e72b" - integrity sha512-qftQXnX1DzpSV8EddtHIT0eDDEiBF8ywhFYR2lI9xrGtxqKN+CvLXhACeCIGbCpQfxxERbrkZEFb8cZcDKbVZA== - throat@^4.0.0, throat@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"