From 23b5450902b6220e39d6813912c3dba3303e5034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 29 Apr 2019 12:04:42 +0200 Subject: [PATCH 1/3] feat: copy all files in init through streams --- packages/cli/package.json | 1 - packages/cli/src/tools/copyAndReplace.js | 8 +++- packages/cli/src/tools/copyFiles.js | 56 +++++++++++++++++++++--- yarn.lock | 34 -------------- 4 files changed, 56 insertions(+), 43 deletions(-) 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/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..a8e56d9aa 100644 --- a/packages/cli/src/tools/copyFiles.js +++ b/packages/cli/src/tools/copyFiles.js @@ -7,8 +7,8 @@ * @flow */ +import fs from 'fs'; import path from 'path'; -import copyAndReplace from './copyAndReplace'; import walk from './walk'; /** @@ -17,12 +17,56 @@ import walk from './walk'; function copyFiles(srcPath: string, destPath: string) { walk(srcPath).forEach(absoluteSrcFilePath => { const relativeFilePath = path.relative(srcPath, absoluteSrcFilePath); - copyAndReplace( - absoluteSrcFilePath, - path.resolve(destPath, relativeFilePath), - {}, // no replacements - ); + 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; + } + + copyBinaryFile(srcPath, destPath, err => { + if (err) { + throw err; + } + }); +} + +/** + * 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); + readStream.on('error', err => { + done(err); + }); + const writeStream = fs.createWriteStream(destPath); + writeStream.on('error', err => { + done(err); + }); + writeStream.on('close', () => { + done(); + }); + writeStream.on('ready', () => { + 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" From be21c8db5f589249a8a56b2de6440e752be21eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 29 Apr 2019 15:57:18 +0200 Subject: [PATCH 2/3] fix asynchrony --- .../init/__tests__/editTemplate.test.js | 18 +++++++---- .../commands/init/__tests__/template.test.js | 4 +-- .../cli/src/commands/init/editTemplate.js | 4 +-- packages/cli/src/commands/init/init.js | 8 +++-- packages/cli/src/commands/init/template.js | 4 +-- packages/cli/src/tools/copyFiles.js | 32 +++++++++++-------- 6 files changed, 43 insertions(+), 27 deletions(-) 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..70ce627e9 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'; @@ -30,7 +30,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/copyFiles.js b/packages/cli/src/tools/copyFiles.js index a8e56d9aa..6f8206a05 100644 --- a/packages/cli/src/tools/copyFiles.js +++ b/packages/cli/src/tools/copyFiles.js @@ -14,11 +14,16 @@ 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); - copyFile(absoluteSrcFilePath, path.resolve(destPath, relativeFilePath)); - }); +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), + ); + }), + ); } /** @@ -33,10 +38,13 @@ function copyFile(srcPath: string, destPath: string) { return; } - copyBinaryFile(srcPath, destPath, err => { - if (err) { - throw err; - } + return new Promise((resolve, reject) => { + copyBinaryFile(srcPath, destPath, err => { + if (err) { + reject(err); + } + resolve(destPath); + }); }); } @@ -47,17 +55,15 @@ 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); }); - const writeStream = fs.createWriteStream(destPath); writeStream.on('error', err => { done(err); }); - writeStream.on('close', () => { + readStream.on('close', () => { done(); - }); - writeStream.on('ready', () => { fs.chmodSync(destPath, mode); }); readStream.pipe(writeStream); From 8ab099127875122a31e7aea482739f04c4a293fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 30 Apr 2019 16:53:45 +0200 Subject: [PATCH 3/3] only replace file if necessary --- packages/cli/src/commands/init/editTemplate.js | 9 +++++---- packages/cli/src/tools/copyFiles.js | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/init/editTemplate.js b/packages/cli/src/commands/init/editTemplate.js index 70ce627e9..a72ca07f3 100644 --- a/packages/cli/src/commands/init/editTemplate.js +++ b/packages/cli/src/commands/init/editTemplate.js @@ -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) { diff --git a/packages/cli/src/tools/copyFiles.js b/packages/cli/src/tools/copyFiles.js index 6f8206a05..795f6862c 100644 --- a/packages/cli/src/tools/copyFiles.js +++ b/packages/cli/src/tools/copyFiles.js @@ -53,7 +53,7 @@ function copyFile(srcPath: string, destPath: string) { */ function copyBinaryFile(srcPath, destPath, cb) { let cbCalled = false; - const {mode} = fs.statSync(srcPath); + // const {mode} = fs.statSync(srcPath); const readStream = fs.createReadStream(srcPath); const writeStream = fs.createWriteStream(destPath); readStream.on('error', err => { @@ -64,7 +64,10 @@ function copyBinaryFile(srcPath, destPath, cb) { }); readStream.on('close', () => { done(); - fs.chmodSync(destPath, mode); + // 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) {