Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions docs/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,25 @@ Every custom template needs to have configuration file called `template.config.j

```js
module.exports = {
// Placeholder name that will be replaced in package.json, index.json, android/, ios/ for a project name.
placeholderName: 'ProjectName',

// Placeholder title that will be replaced in values.xml and Info.plist with title provided by the user.
// We default this value to 'Hello App Display Name', which is default placeholder in react-native template.
titlePlaceholder: 'Hello App Display Name',

// Directory with the template which will be copied and processed by React Native CLI. Template directory should have package.json with all dependencies specified, including `react-native`.
templateDir: './template',

// Path to script, which will be executed after initialization process, but before installing all the dependencies specified in the template. This script runs as a shell script but you can change that (e.g. to Node) by using a shebang (see example custom template).
postInitScript: './script.js',
templateDir: "./template",

placeholders: {
hermes_flag:true,

slug:"my_project_name",

// Placeholder name that will be replaced in package.json, index.json, android/, ios/ for a project name.
name: 'MyProjectName', // if you override this name, than you can use --title arg in cli

// title that will be replaced in values.xml and Info.plist with title provided by the user.
// We default this value to 'Hello App Display Name', which is default placeholder in react-native template.
title: 'Hello App Display Name'
},
// Path to script, which will be executed after init
postInitScript: "./script.js"
};
```

You can find example custom template [here](https://github.com/Esemesek/react-native-new-template).
You can find example custom template [here](https://github.com/nomi9995/react-native-template-placeholder).

for add more placeholders, you can add a placeholder into the `placeholders` object as mentioned in `template.config.js` and you can add that variable in template file like this `<%= slug %>` by using [Embedded JavaScript templating](https://ejs.co/#docs)
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@react-native-community/cli-config": "^7.0.1",
"chalk": "^4.1.2",
"commander": "^2.19.0",
"ejs": "^3.1.6",
"execa": "^1.0.0",
"find-up": "^4.1.0",
"fs-extra": "^8.1.0",
Expand All @@ -48,6 +49,7 @@
"react-native": "*"
},
"devDependencies": {
"@types/ejs": "^3.1.0",
"@types/fs-extra": "^8.1.0",
"@types/graceful-fs": "^4.1.3",
"@types/hapi__joi": "^17.1.6",
Expand Down
141 changes: 50 additions & 91 deletions packages/cli/src/commands/init/editTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,72 @@
import path from 'path';
import {logger} from '@react-native-community/cli-tools';
import walk from '../../tools/walk';

import ejs from 'ejs';
const BINARIES = /(gradlew|\.(jar|keystore|png|jpg|gif))$/;
// We need `graceful-fs` behavior around async file renames on Win32.
// `gracefulify` does not support patching `fs.promises`. Use `fs-extra`, which
// exposes its own promise-based interface over `graceful-fs`.
import fs from 'fs-extra';

interface PlaceholderConfig {
projectName: string;
placeholderName: string;
placeholderTitle?: string;
projectTitle?: string;
}

/**
TODO: This is a default placeholder for title in react-native template.
We should get rid of this once custom templates adapt `placeholderTitle` in their configurations.
*/
const DEFAULT_TITLE_PLACEHOLDER = 'Hello App Display Name';
type placeholdersType = {[key: string]: any};

async function replaceNameInUTF8File(
filePath: string,
projectName: string,
templateName: string,
export function overridePlaceholderTitle(
projectTitle?: string,
placeholders?: placeholdersType,
) {
logger.debug(`Replacing in ${filePath}`);
const fileContent = await fs.readFile(filePath, 'utf8');
const replacedFileContent = fileContent
.replace(new RegExp(templateName, 'g'), projectName)
.replace(
new RegExp(templateName.toLowerCase(), 'g'),
projectName.toLowerCase(),
);

if (fileContent !== replacedFileContent) {
await fs.writeFile(filePath, replacedFileContent, 'utf8');
if (projectTitle && placeholders) {
placeholders.title = projectTitle;
}
}

async function renameFile(filePath: string, oldName: string, newName: string) {
const newFileName = path.join(
path.dirname(filePath),
path.basename(filePath).replace(new RegExp(oldName, 'g'), newName),
export async function copyTemplateAndReplacePlaceholders(
templateName: string,
templateDir: string,
templateSourceDir: string,
placeholders: placeholdersType = {},
) {
const templatePath = path.resolve(
templateSourceDir,
'node_modules',
templateName,
templateDir,
);

logger.debug(`Renaming ${filePath} -> file:${newFileName}`);

await fs.rename(filePath, newFileName);
}
const dest = process.cwd();

function shouldRenameFile(filePath: string, nameToReplace: string) {
return path.basename(filePath).includes(nameToReplace);
}
logger.debug(
`Copying template from ${templatePath} and replace placeholders`,
);

function shouldIgnoreFile(filePath: string) {
return filePath.match(/node_modules|yarn.lock|package-lock.json/g);
await CopyDirWithReplacePlaceholders(templatePath, dest, placeholders);
}

const UNDERSCORED_DOTFILES = [
'buckconfig',
'eslintrc.js',
'flowconfig',
'gitattributes',
'gitignore',
'prettierrc.js',
'watchmanconfig',
'editorconfig',
'bundle',
'ruby-version',
];

async function processDotfiles(filePath: string) {
const dotfile = UNDERSCORED_DOTFILES.find((e) => filePath.includes(`_${e}`));

if (dotfile === undefined) {
return;
}

await renameFile(filePath, `_${dotfile}`, `.${dotfile}`);
}
export const CopyDirWithReplacePlaceholders = async (
source: string,
dest: string,
placeholders: placeholdersType = {},
) => {
await fs.mkdirp(dest);

const files = await fs.readdir(source);
for (const f of files) {
const target = path.join(
dest,
ejs.render(f.replace(/^\$/, ''), placeholders, {
openDelimiter: '{',
closeDelimiter: '}',
}),
);

export async function changePlaceholderInTemplate({
projectName,
placeholderName,
placeholderTitle = DEFAULT_TITLE_PLACEHOLDER,
projectTitle = projectName,
}: PlaceholderConfig) {
logger.debug(`Changing ${placeholderName} for ${projectName} in template`);
const file = path.join(source, f);
const stats = await fs.stat(file);

for (const filePath of walk(process.cwd()).reverse()) {
if (shouldIgnoreFile(filePath)) {
continue;
if (stats.isDirectory()) {
await CopyDirWithReplacePlaceholders(file, target, placeholders);
} else if (!file.match(BINARIES)) {
const content = await fs.readFile(file, 'utf8');
await fs.writeFile(target, ejs.render(content, placeholders));
} else {
await fs.copyFile(file, target);
}
if (!(await fs.stat(filePath)).isDirectory()) {
await replaceNameInUTF8File(filePath, projectName, placeholderName);
await replaceNameInUTF8File(filePath, projectTitle, placeholderTitle);
}
if (shouldRenameFile(filePath, placeholderName)) {
await renameFile(filePath, placeholderName, projectName);
}
if (shouldRenameFile(filePath, placeholderName.toLowerCase())) {
await renameFile(
filePath,
placeholderName.toLowerCase(),
projectName.toLowerCase(),
);
}

await processDotfiles(filePath);
}
}
};
29 changes: 10 additions & 19 deletions packages/cli/src/commands/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
import {
installTemplatePackage,
getTemplateConfig,
copyTemplate,
executePostInitScript,
} from './template';
import {changePlaceholderInTemplate} from './editTemplate';
import {
copyTemplateAndReplacePlaceholders,
overridePlaceholderTitle,
} from './editTemplate';
import * as PackageManager from '../../tools/packageManager';
import {installPods} from '@react-native-community/cli-doctor';
import banner from './banner';
Expand All @@ -34,7 +36,6 @@ type Options = {
};

interface TemplateOptions {
projectName: string;
templateUri: string;
npm?: boolean;
directory: string;
Expand Down Expand Up @@ -76,7 +77,6 @@ function getTemplateName(cwd: string) {
}

async function createFromTemplate({
projectName,
templateUri,
npm,
directory,
Expand All @@ -99,26 +99,19 @@ async function createFromTemplate({
await installTemplatePackage(templateUri, templateSourceDir, npm);

loader.succeed();
loader.start('Copying template');
loader.start('Copying template & Processing template');

const templateName = getTemplateName(templateSourceDir);
const templateConfig = getTemplateConfig(templateName, templateSourceDir);
await copyTemplate(
const placeholders = templateConfig.placeholders || {};
overridePlaceholderTitle(projectTitle, placeholders);
await copyTemplateAndReplacePlaceholders(
templateName,
templateConfig.templateDir,
templateSourceDir,
placeholders,
);

loader.succeed();
loader.start('Processing template');

await changePlaceholderInTemplate({
projectName,
projectTitle,
placeholderName: templateConfig.placeholderName,
placeholderTitle: templateConfig.titlePlaceholder,
});

loader.succeed();
const {postInitScript} = templateConfig;
if (postInitScript) {
Expand Down Expand Up @@ -177,15 +170,13 @@ async function installDependencies({
}

async function createProject(
projectName: string,
directory: string,
version: string,
options: Options,
) {
const templateUri = options.template || `react-native@${version}`;

return createFromTemplate({
projectName,
templateUri,
npm: options.npm,
directory,
Expand All @@ -211,7 +202,7 @@ export default (async function initialize(
const directoryName = path.relative(root, options.directory || projectName);

try {
await createProject(projectName, directoryName, version, options);
await createProject(directoryName, version, options);

const projectFolder = path.join(root, directoryName);
printRunInstructions(projectFolder, projectName);
Expand Down
26 changes: 4 additions & 22 deletions packages/cli/src/commands/init/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import execa from 'execa';
import path from 'path';
import {logger, CLIError} from '@react-native-community/cli-tools';
import * as PackageManager from '../../tools/packageManager';
import copyFiles from '../../tools/copyFiles';
import replacePathSepForRegex from '../../tools/replacePathSepForRegex';
import fs from 'fs';
import chalk from 'chalk';

export type TemplateConfig = {
placeholderName: string;
templateDir: string;
postInitScript?: string;
titlePlaceholder?: string;
placeholders?: {
placeholderName?: string;
[key: string]: any;
};
};

export async function installTemplatePackage(
Expand Down Expand Up @@ -57,25 +58,6 @@ export function getTemplateConfig(
return require(configFilePath);
}

export async function copyTemplate(
templateName: string,
templateDir: string,
templateSourceDir: string,
) {
const templatePath = path.resolve(
templateSourceDir,
'node_modules',
templateName,
templateDir,
);

logger.debug(`Copying template from ${templatePath}`);
let regexStr = path.resolve(templatePath, 'node_modules');
await copyFiles(templatePath, process.cwd(), {
exclude: [new RegExp(replacePathSepForRegex(regexStr))],
});
}

export function executePostInitScript(
templateName: string,
postInitScript: string,
Expand Down
Loading