Skip to content

Commit

Permalink
Merge pull request #7 from bachmateus/development
Browse files Browse the repository at this point in the history
Release v2.1.1
  • Loading branch information
bachmateus authored Oct 21, 2022
2 parents ca2fde6 + fe1a732 commit a17c8db
Show file tree
Hide file tree
Showing 35 changed files with 693 additions and 68 deletions.
37 changes: 29 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Works on Windows, Linux and MacOS

![](logo.png)
Obs: On Linux, sudo command is required

# CAUTION
DO NOT use this project in a production project due to it still being under development
Expand All @@ -20,8 +20,8 @@ You can install via npm:
# Features
Create React Native files.

1. Create component/screen files using JavaScript.
2. Create component/screen files using TypeScript.
1. Create component/screen/route files using JavaScript.
2. Create component/screen/route files using TypeScript.
3. Create a config file to store your preferences.

# Project folders structure
Expand All @@ -37,42 +37,62 @@ Create React Native files.
+-- ScreenName
+-- index.js
+-- styles.js
+-- routes
+-- index.js
+-- route-name.routes.js
+-- rn-files-creator.json
```


# Usage
The basic use of rn-file-creator is :
```bash
rn -command componentName [otherComponentName]
rn -command [ComponentName] [otherComponentName]
```

## Creating a Component
Type the following command to create a component

```bash
rn -c ComponentName
rn -c [ComponentName]
```

You can use more than one arg per time to create more than one component

```bash
rn -c ComponentName1 -c ComponentName2 -c ComponentName3 -c ComponentName4
rn -c [ComponentName1] -c [ComponentName2] -c [ComponentName3] -c [ComponentName4]
```

## Creating a Screen
Type the following command to create a screen

```bash
rn -s ScreenName
rn -s [ScreenName]
```

You can use more than one arg per time to create more than one screen

```bash
rn -s ScreenName1 -s ScreenName2 -s ScreenName3 -s ScreenName4
rn -s [ScreenName1] -s [ScreenName2] -s [ScreenName3] -s [ScreenName4]
```

## Creating a route
Type the following command to create a route

```bash
rn -r [RouteName]
```

You can specify a navigator type (stack, bottomTab or drawer) passing -t param.

```bash
rn -r [RouteName] -t [NavigatorType]
```

Obs: If you don't specify the navigator type it will be asked to you.

Each created route is included on main route (index file). This file is generated automaticaly.

## Creating guide
You can follow the guide to create a component if you do not want to type the entire command.

Expand All @@ -89,6 +109,7 @@ After typing enter just follow the guide.
| ----------------- | ------------------------------------------------------------- |
| -c, --component | Create one or more components |
| -h, --help | Show possible commands |
| -r, --route | Create one route |
| -s, --screen | Create one or more screens |

# Inspiration
Expand Down
12 changes: 7 additions & 5 deletions dev/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ export const defaultArgsPrompt = [

const commands = {
empty: "rn",
createComponent: "rn -c Component1 -i Login -h",
createComponent: "rn -c Component1 -c Component2",
createComponentShort: "rn --screen A4 --component A4",
createNavigator: "rn -n Login -n Logged -t stack -i MainNavigator -aaps",
// createNavigatorShort: "rn -c Component1",
createRoute: "rn -r RecipeRoutes -t bottomTab",
// createRoute: "rn -r Login -r Logged -t stack -i MainRoute -aaps",
// createRoute: "rn -r Login -r Logged -t stack -i MainRoute -aaps",
// createRouteShort: "rn -c Component1",
}

const argCommand = commands.createComponentShort.split(' ');
const argCommand = commands.createRoute.split(' ');
// const argCommand = commands.createComponent.split(' ');
// const argCommand = commands.empty.split(' ');
// const argCommand = commands.createNavigator.split(' ');
// const argCommand = commands.createRoute.split(' ');
argCommand.shift();

export const args = [...defaultArgsPrompt, ...argCommand];
26 changes: 15 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bachmateus/rn-files-creator",
"version": "2.0.4",
"version": "2.1.1",
"description": "rn-files-creator make easier to create new React Native components files. It provides a basic file structure for your components with StyleSheet or StyledComponent.",
"main": "src/main.js",
"bin": {
Expand All @@ -10,18 +10,22 @@
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/bachmateus/rn-files-creator.git"
},
"scripts": {
"start": "SET NODE_ENVIRONMENT=development & ts-node dev/index.ts",
"start:dev": "SET NODE_ENVIRONMENT=development & npx nodemon dev/index.ts",
"build": "npx tsc -p tsconfig-build.json & npm run copy:files",
"build:dev": "npm run build & npm run copy:files & npm link",
"test": "SET NODE_ENVIRONMENT=development & jest --runInBand",
"test:watch": "SET NODE_ENVIRONMENT=development & jest --runInBand --watch",
"test:coverage": "SET NODE_ENVIRONMENT=development & jest --coverage --runInBand",
"copy:files": "npm run copy:template-files & npm run copy:assets-files",
"copy:template-files": "cp -R ./src/builder/templates/ ./build/builder/",
"start": "env NODE_ENVIRONMENT=development ts-node dev/index.ts",
"start:dev": "env NODE_ENVIRONMENT=development npx nodemon dev/index.ts",
"build": "npx tsc -p tsconfig-build.json && npm run copy:files",
"build:dev": "npm run build && npm link",
"test": "env NODE_ENVIRONMENT=development jest --runInBand",
"test:watch": "env NODE_ENVIRONMENT=development jest --runInBand --watch",
"test:coverage": "env NODE_ENVIRONMENT=development jest --coverage --runInBand",
"copy:files": "npm run copy:template-files && npm run copy:assets-files",
"copy:template-files": "cp -R ./src/builder/templates/ ./build/builder/templates/",
"copy:assets-files": "cp -R ./src/cli/assets/ ./build/cli/assets/",
"deploy": "npm run build & npm publish"
"deploy:npm": "npm run build && npm publish"
},
"keywords": [
"cli",
Expand Down
4 changes: 3 additions & 1 deletion src/builder/builder.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { filesManagerService } from "../manager/manager.module";
import { ComponentBuilderService } from "./service/component-builder.service";
import { ScreenBuilderService } from "./service/screen-builder.service";
import { RouteBuilderService } from "./service/route-builder.service";

export const componentBuilderService = new ComponentBuilderService(filesManagerService);
export const screenBuilderService = new ScreenBuilderService(filesManagerService);
export const screenBuilderService = new ScreenBuilderService(filesManagerService);
export const routeBuilderService = new RouteBuilderService(filesManagerService);
4 changes: 2 additions & 2 deletions src/builder/data/component-files-to-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ export const componentFilesToCopy = {
},
TypeScript: {
StyleSheet: [
{ templateFileName: 'index.StyleSheet.ts', fileName: 'index.ts', shallRename: true },
{ templateFileName: 'index.StyleSheet.ts', fileName: 'index.tsx', shallRename: true },
{ templateFileName: 'styles.StyleSheet.js', fileName: 'styles.ts', shallRename: false },
],
'styled-component': [
{ templateFileName: 'index.StyledComponent.ts', fileName: 'index.ts', shallRename: true },
{ templateFileName: 'index.StyledComponent.ts', fileName: 'index.tsx', shallRename: true },
{ templateFileName: 'styles.StyledComponent.js', fileName: 'styles.ts', shallRename: false },
]
},
Expand Down
34 changes: 34 additions & 0 deletions src/builder/data/route-constants.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { generateRouteNameFile, getRouteNameByFileName } from './route-constants';

describe('route-words-rename', () => {
describe('generateRouteNameFile', () => {
it('success to generate component file name composed by one word', () => {
const componentName = 'LoggedRoutes';
const fileName = 'logged.routes';
expect(generateRouteNameFile(componentName)).toEqual(fileName);
});
it('success to generate component file name composed by more than one word', () => {
const componentName = 'UserLoggedRoutes';
const fileName = 'user-logged.routes';
expect(generateRouteNameFile(componentName)).toEqual(fileName);
});
it('success to generate component file name composed by lowercase words', () => {
const componentName = 'user-logged-routes';
const fileName = 'user-logged.routes';
expect(generateRouteNameFile(componentName)).toEqual(fileName);
});
})

describe('getRouteNameByFileName', () => {
it('success to get component name composed by one word', () => {
const fileName = 'logged.routes.js';
const componentName = 'LoggedRoutes';
expect(getRouteNameByFileName(fileName)).toEqual(componentName);
});
it('success to get component name composed by more than one word', () => {
const fileName = 'user-logged.routes.js';
const componentName = 'UserLoggedRoutes';
expect(getRouteNameByFileName(fileName)).toEqual(componentName);
});
})
});
68 changes: 68 additions & 0 deletions src/builder/data/route-constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
export const routeWordsToRename = {
// TODO: change interface to use routesTypesEnum
routeFile: {
routeNameToRename: 'MyRoutes',
library: {
stack: [
{ find: 'ROUTE_CREATOR', replaceTo: 'createNativeStackNavigator' },
{ find: 'ROUTE_LIB', replaceTo: '@react-navigation/native-stack' },
],
bottomTab: [
{ find: 'ROUTE_CREATOR', replaceTo: 'createBottomTabNavigator' },
{ find: 'ROUTE_LIB', replaceTo: '@react-navigation/bottom-tabs' },
],
drawer: [
{ find: 'ROUTE_CREATOR', replaceTo: 'createDrawerNavigator' },
{ find: 'ROUTE_LIB', replaceTo: '@react-navigation/drawer' },
],
},
}
}

export const routeFileToCopy = {
JavaScript:(routeName:string) => ({
routeName, templateFileName: 'routes.js', fileName: `${generateRouteNameFile(routeName)}.js`
}),
TypeScript:(routeName:string) => ({
routeName, templateFileName: 'routes.js', fileName: `${generateRouteNameFile(routeName)}.tsx`
}),
}

export const mainRouteFileName = {
JavaScript: { templateFileName: 'main.routes.js', fileName: 'index.js'},
TypeScript: { templateFileName: 'main.routes.js', fileName: 'index.tsx'},
};

export const generateRouteNameFile = (componentRouteName:string) => {
const routeName = componentRouteName.replace('Routes', '').replace('-routes', '');
if (routeName.length === 0) return componentRouteName.toLocaleLowerCase();
const lowercaseName = routeName
.split('')
.map(char => {
if(char === char.toUpperCase()) return `-${char.toLocaleLowerCase()}`;
return char;
})
.join('')
.replace('-', '');
return `${lowercaseName}.routes`.toLocaleLowerCase();
}

export const getRouteNameByFileName = (fileName:string): string => {
const fileNameWithouExtension = fileName.replace('.tsx', '').replace('.js', '');
return fileNameWithouExtension
.split('-')
.map(word=>word.charAt(0).toUpperCase() + word.slice(1))
.join('')
.replace('.routes', 'Routes');
}

export const generateImportCodeLine = (routeName: string): string => `import ${routeName.split('.')[0]} from './${generateRouteNameFile(routeName)}'`;

export const generateJsxCodeLine = (routeName: string, isMainRoute: boolean): string => isMainRoute ? ` <${routeName} />` : ` <Screen name="${routeName}" component={${routeName}} />` ;

export const getMainRoutePathAndTagReference = (mainRouteName: string) => {
return {
importTermToFind: 'import',
nestedRouteTagReference: (!mainRouteName) ? '<NavigationContainer' : '<Navigator',
}
}
100 changes: 100 additions & 0 deletions src/builder/service/route-builder.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { configFileLanguageType, RnFilesCreatorConfigFile } from '../../cli/data/rn-files-creator-config-file';
import { FilesManagerService } from "../../manager/service/files-manager.service";
import { RouteCliParams } from '../../cli/data/creator-params';
import { cliTemplatePath, userProjectDirectory } from '../../manager/constants/paths';
import { ComponentAlreadyExistsLogger } from '../logger/component-already-exists.logger';
import { NativeStringFunction } from '../../common/functions/nativeStringFunction';
import { generateImportCodeLine, generateJsxCodeLine, getMainRoutePathAndTagReference, mainRouteFileName, routeFileToCopy, routeWordsToRename } from '../data/route-constants';
import { routesTypesEnum } from '../../cli/data/args-cli-options';

export class RouteBuilderService {
private routeCliParams: RouteCliParams = {} as RouteCliParams
private projectConfig: RnFilesCreatorConfigFile = {} as RnFilesCreatorConfigFile

constructor(private readonly filesManagerService: FilesManagerService) {}

async handle(routeCliParams: RouteCliParams, projectConfig: RnFilesCreatorConfigFile) {
this.setConfigVars(routeCliParams, projectConfig);
const wasRouteCreated = await this.createRoute();

// TODO: include route on main route or where it belongs
if(wasRouteCreated) await this.includeRouteOnMainRoute(this.routeCliParams.route, '', projectConfig.language);
return wasRouteCreated;
// TODO: return which libs the cli shall install
}

private setConfigVars(routeCliParams: RouteCliParams, projectConfig: RnFilesCreatorConfigFile) {
this.routeCliParams = routeCliParams
this.projectConfig = projectConfig
}

private async createRoute(): Promise<boolean> {
// TODO: if fail remove dir
const { fileName, templateFileName} = routeFileToCopy[this.projectConfig.language](this.routeCliParams.route);
if (await this.checkIfRouteExists(fileName)) return false;
return await this.createRouteFile(this.routeCliParams.route, this.routeCliParams.routeType, fileName, templateFileName);
}

private async checkIfRouteExists(fileName: string): Promise<boolean> {
const routeName = this.routeCliParams.route;
const doesExist = await this.filesManagerService.checkIfPathExists(`${userProjectDirectory.route}\/${fileName}`);
if (doesExist) new ComponentAlreadyExistsLogger(routeName);
const doesPathExist = await this.filesManagerService.checkIfPathExists(`${userProjectDirectory.route}\/`);
if (!doesPathExist) await this.filesManagerService.createDirectory(userProjectDirectory.route)
return doesExist;
}

private async createRouteFile(routeName: string, routeType: routesTypesEnum, fileName: string, templateFileName: string): Promise<boolean> {
const fileData = await this.filesManagerService.readFile(`${cliTemplatePath.route}\/${templateFileName}`);
const newContent = this.replaceWordsFromTemplateFile(fileData!, routeName, routeType);
return await this.filesManagerService.writeFile(userProjectDirectory.route, fileName, newContent)
}

private replaceWordsFromTemplateFile(content: string, routeName: string, routeType: routesTypesEnum): string {
const libraryWords = routeWordsToRename.routeFile.library[routeType];
let newContent = content;
newContent = NativeStringFunction.replaceAll(newContent, routeWordsToRename.routeFile.routeNameToRename, routeName);
libraryWords.forEach(item => newContent = NativeStringFunction.replaceAll(newContent, item.find, item.replaceTo));
return newContent;
}

private async createMainRouteFile(language: configFileLanguageType): Promise<boolean> {
const mainFileExists = await this.filesManagerService.checkIfPathExists(userProjectDirectory.route + mainRouteFileName[language].fileName);
if(mainFileExists) return false;
return await this.filesManagerService.copyFile(
`${cliTemplatePath.route}\/${mainRouteFileName[language].templateFileName}`,
userProjectDirectory.route,
mainRouteFileName[language].fileName
);
}

private async includeRouteOnMainRoute(routeName: string, nestedRouteName: string, language: configFileLanguageType): Promise<boolean> {
const { nestedRouteTagReference, importTermToFind } = getMainRoutePathAndTagReference(nestedRouteName);
const routeFileName = (!nestedRouteName) ? mainRouteFileName[language].fileName : routeFileToCopy[this.projectConfig.language](nestedRouteName).fileName;
if(!nestedRouteName) await this.createMainRouteFile(language);

const nestedRouteData = await this.filesManagerService.readFile(userProjectDirectory.route + routeFileName);
if(!nestedRouteData) return false;
const codeLines = nestedRouteData?.split('\n') as string[];
// TODO: convert app.route.tsx to AppRoute
const lineOfLastImport = this.getLineNumberFromLastOcorrency(codeLines, importTermToFind)
codeLines?.splice(lineOfLastImport+1, 0, generateImportCodeLine(routeName));

// TODO: get space number from the file
const lineOfNavigatorContentTag = this.getLineNumberFromLastOcorrency(codeLines, nestedRouteTagReference)
codeLines?.splice(lineOfNavigatorContentTag+1, 0, generateJsxCodeLine(routeName, !nestedRouteName));

return await this.filesManagerService.updateFile(userProjectDirectory.route, routeFileName, codeLines?.join('\n'))
}

private getLineNumberFromLastOcorrency(codeLines: string[], textToFind: string): number {
return codeLines?.reduce(
(previousValue, codeLine: string, index) => {
const found = codeLine.indexOf(textToFind);
if (found > -1) return index;
return previousValue;
},
-1
) as number;
}
}
Loading

0 comments on commit a17c8db

Please sign in to comment.