Skip to content

Commit

Permalink
feat: copy all files in init through streams (#363)
Browse files Browse the repository at this point in the history
* feat: copy all files in init through streams

* fix asynchrony

* only replace file if necessary
  • Loading branch information
thymikee authored Apr 30, 2019
1 parent 93e3eb6 commit c7fe3a3
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 64 deletions.
1 change: 0 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 12 additions & 6 deletions packages/cli/src/commands/init/__tests__/editTemplate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -74,6 +82,4 @@ test('should edit template', () => {
expect(
snapshotDiff(fixtureTree, transformedTree, {contextLines: 1}),
).toMatchSnapshot();

fs.removeSync(testPath);
});
4 changes: 2 additions & 2 deletions packages/cli/src/commands/init/__tests__/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ test('getTemplateConfig', () => {
);
});

test('copyTemplate', () => {
test('copyTemplate', async () => {
const TEMPLATE_DIR = 'some/dir';
const CWD = '.';

jest.spyOn(path, 'resolve').mockImplementationOnce((...e) => e.join('/'));
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,
Expand Down
13 changes: 7 additions & 6 deletions packages/cli/src/commands/init/editTemplate.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/commands/init/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/init/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function getTemplateConfig(
return require(configFilePath);
}

export function copyTemplate(
export async function copyTemplate(
templateName: string,
templateDir: string,
templateSourceDir: string,
Expand All @@ -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(
Expand Down
8 changes: 6 additions & 2 deletions packages/cli/src/tools/copyAndReplace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down
71 changes: 62 additions & 9 deletions packages/cli/src/tools/copyFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
34 changes: 0 additions & 34 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit c7fe3a3

Please sign in to comment.