react-native-architecture-generator 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/dist/bin/rn_arch_gen.d.ts +2 -0
- package/dist/bin/rn_arch_gen.js +41 -0
- package/dist/lib/commands/feature.d.ts +3 -0
- package/dist/lib/commands/feature.js +52 -0
- package/dist/lib/commands/init.d.ts +3 -0
- package/dist/lib/commands/init.js +75 -0
- package/dist/lib/commands/model.d.ts +3 -0
- package/dist/lib/commands/model.js +94 -0
- package/dist/lib/commands/screen.d.ts +4 -0
- package/dist/lib/commands/screen.js +82 -0
- package/dist/lib/index.d.ts +12 -0
- package/dist/lib/index.js +12 -0
- package/dist/lib/models/config.d.ts +27 -0
- package/dist/lib/models/config.js +27 -0
- package/dist/lib/templates/base-templates.d.ts +18 -0
- package/dist/lib/templates/base-templates.js +453 -0
- package/dist/lib/utils/config-helper.d.ts +6 -0
- package/dist/lib/utils/config-helper.js +27 -0
- package/dist/lib/utils/feature-helper.d.ts +24 -0
- package/dist/lib/utils/feature-helper.js +400 -0
- package/dist/lib/utils/file-helper.d.ts +4 -0
- package/dist/lib/utils/file-helper.js +62 -0
- package/dist/lib/utils/packagejson-helper.d.ts +4 -0
- package/dist/lib/utils/packagejson-helper.js +76 -0
- package/dist/lib/utils/string-utils.d.ts +9 -0
- package/dist/lib/utils/string-utils.js +28 -0
- package/package.json +72 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { StringUtils } from '../utils/string-utils.js';
|
|
5
|
+
import { ConfigHelper } from '../utils/config-helper.js';
|
|
6
|
+
import { Architecture } from '../models/config.js';
|
|
7
|
+
export class ModelCommand {
|
|
8
|
+
static async run(name, feature) {
|
|
9
|
+
const config = await ConfigHelper.getConfig();
|
|
10
|
+
const spinner = ora(`📦 Generating model: ${name}...`).start();
|
|
11
|
+
try {
|
|
12
|
+
const pascalName = StringUtils.toPascalCase(name);
|
|
13
|
+
const snakeName = StringUtils.toSnakeCase(name);
|
|
14
|
+
const camelName = StringUtils.toCamelCase(name);
|
|
15
|
+
// Determine model path based on architecture
|
|
16
|
+
const arch = config?.architecture ?? Architecture.cleanArchitecture;
|
|
17
|
+
let modelDir;
|
|
18
|
+
let modelContent;
|
|
19
|
+
switch (arch) {
|
|
20
|
+
case Architecture.cleanArchitecture:
|
|
21
|
+
modelDir = 'data/models';
|
|
22
|
+
modelContent = `export interface ${pascalName} {
|
|
23
|
+
id: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ${pascalName}Model implements ${pascalName} {
|
|
27
|
+
id: number;
|
|
28
|
+
|
|
29
|
+
constructor(data: { id: number }) {
|
|
30
|
+
this.id = data.id;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static fromJson(json: Record<string, unknown>): ${pascalName}Model {
|
|
34
|
+
return new ${pascalName}Model({ id: json['id'] as number });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toJson(): Record<string, unknown> {
|
|
38
|
+
return { id: this.id };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
break;
|
|
43
|
+
case Architecture.mvvm:
|
|
44
|
+
modelDir = 'models';
|
|
45
|
+
modelContent = `export interface ${pascalName}Model {
|
|
46
|
+
id: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const create${pascalName} = (data: Partial<${pascalName}Model>): ${pascalName}Model => ({
|
|
50
|
+
id: data.id ?? 0,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const ${camelName}FromJson = (json: Record<string, unknown>): ${pascalName}Model => ({
|
|
54
|
+
id: json['id'] as number,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const ${camelName}ToJson = (model: ${pascalName}Model): Record<string, unknown> => ({
|
|
58
|
+
id: model.id,
|
|
59
|
+
});
|
|
60
|
+
`;
|
|
61
|
+
break;
|
|
62
|
+
case Architecture.featureBased:
|
|
63
|
+
case Architecture.atomicDesign:
|
|
64
|
+
modelDir = 'types';
|
|
65
|
+
modelContent = `export interface ${pascalName} {
|
|
66
|
+
id: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ${pascalName}CreateInput {
|
|
70
|
+
id?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ${pascalName}UpdateInput {
|
|
74
|
+
id?: number;
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const targetDir = feature
|
|
80
|
+
? path.join(process.cwd(), 'src', 'features', StringUtils.toSnakeCase(feature), modelDir)
|
|
81
|
+
: path.join(process.cwd(), 'src', 'core', 'models');
|
|
82
|
+
const fileName = arch === Architecture.featureBased || arch === Architecture.atomicDesign
|
|
83
|
+
? `${camelName}.types.ts`
|
|
84
|
+
: `${pascalName}Model.ts`;
|
|
85
|
+
const filePath = path.join(targetDir, fileName);
|
|
86
|
+
await fs.ensureDir(targetDir);
|
|
87
|
+
await fs.writeFile(filePath, modelContent);
|
|
88
|
+
spinner.succeed(`Model ${fileName} generated in ${path.relative(process.cwd(), targetDir)}! ✅`);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
spinner.fail(`Failed to generate model: ${error}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { StringUtils } from '../utils/string-utils.js';
|
|
5
|
+
import { ConfigHelper } from '../utils/config-helper.js';
|
|
6
|
+
import { Architecture, Routing } from '../models/config.js';
|
|
7
|
+
export class ScreenCommand {
|
|
8
|
+
static async run(name, feature) {
|
|
9
|
+
const config = await ConfigHelper.getConfig();
|
|
10
|
+
const spinner = ora(`📄 Generating screen: ${name}...`).start();
|
|
11
|
+
try {
|
|
12
|
+
const pascalName = StringUtils.toPascalCase(name);
|
|
13
|
+
const snakeName = StringUtils.toSnakeCase(name);
|
|
14
|
+
const content = `import React from 'react';
|
|
15
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
16
|
+
|
|
17
|
+
export const ${pascalName}Screen: React.FC = () => {
|
|
18
|
+
return (
|
|
19
|
+
<View style={styles.container}>
|
|
20
|
+
<Text style={styles.title}>${pascalName}</Text>
|
|
21
|
+
<Text style={styles.subtitle}>${pascalName} Screen</Text>
|
|
22
|
+
</View>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const styles = StyleSheet.create({
|
|
27
|
+
container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 16 },
|
|
28
|
+
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
|
|
29
|
+
subtitle: { fontSize: 16, color: '#757575' },
|
|
30
|
+
});
|
|
31
|
+
`;
|
|
32
|
+
// Determine screen path based on architecture
|
|
33
|
+
const arch = config?.architecture ?? Architecture.cleanArchitecture;
|
|
34
|
+
let screenDir;
|
|
35
|
+
switch (arch) {
|
|
36
|
+
case Architecture.mvvm:
|
|
37
|
+
screenDir = 'views/screens';
|
|
38
|
+
break;
|
|
39
|
+
case Architecture.cleanArchitecture:
|
|
40
|
+
screenDir = 'presentation/screens';
|
|
41
|
+
break;
|
|
42
|
+
default: // featureBased, atomicDesign
|
|
43
|
+
screenDir = 'screens';
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
const targetDir = feature
|
|
47
|
+
? path.join(process.cwd(), 'src', 'features', StringUtils.toSnakeCase(feature), screenDir)
|
|
48
|
+
: path.join(process.cwd(), 'src', arch === Architecture.mvvm ? 'views/screens' : arch === Architecture.cleanArchitecture ? 'presentation/screens' : 'screens');
|
|
49
|
+
const filePath = path.join(targetDir, `${pascalName}Screen.tsx`);
|
|
50
|
+
await fs.ensureDir(targetDir);
|
|
51
|
+
await fs.writeFile(filePath, content);
|
|
52
|
+
// Auto-register in navigator
|
|
53
|
+
if (config && config.routing === Routing.reactNavigation) {
|
|
54
|
+
await this.registerInNavigator(pascalName, snakeName, feature, screenDir);
|
|
55
|
+
}
|
|
56
|
+
spinner.succeed(`Screen ${pascalName}Screen generated in ${path.relative(process.cwd(), targetDir)}! ✅`);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
spinner.fail(`Failed to generate screen: ${error}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
static async registerInNavigator(pascalName, snakeName, feature, screenDir = 'presentation/screens') {
|
|
63
|
+
const navFile = path.join(process.cwd(), 'src', 'navigation', 'AppNavigator.tsx');
|
|
64
|
+
if (!(await fs.pathExists(navFile)))
|
|
65
|
+
return;
|
|
66
|
+
let contents = await fs.readFile(navFile, 'utf-8');
|
|
67
|
+
const importPath = feature
|
|
68
|
+
? `../features/${StringUtils.toSnakeCase(feature)}/${screenDir}/${pascalName}Screen`
|
|
69
|
+
: `../presentation/screens/${pascalName}Screen`;
|
|
70
|
+
const screenImport = `import { ${pascalName}Screen } from '${importPath}';`;
|
|
71
|
+
if (!contents.includes(screenImport)) {
|
|
72
|
+
contents = `${screenImport}\n${contents}`;
|
|
73
|
+
}
|
|
74
|
+
if (!contents.includes(`${pascalName}: undefined`)) {
|
|
75
|
+
contents = contents.replace('// Define your route params here', `// Define your route params here\n ${pascalName}: undefined;`);
|
|
76
|
+
}
|
|
77
|
+
if (!contents.includes(`name="${pascalName}"`)) {
|
|
78
|
+
contents = contents.replace('{/* Add your screens here */}', `<Stack.Screen name="${pascalName}" component={${pascalName}Screen} />\n {/* Add your screens here */}`);
|
|
79
|
+
}
|
|
80
|
+
await fs.writeFile(navFile, contents);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { Architecture, ArchitectureLabels, StateManagement, Routing } from './models/config.js';
|
|
2
|
+
export type { GeneratorConfig } from './models/config.js';
|
|
3
|
+
export { InitCommand } from './commands/init.js';
|
|
4
|
+
export { FeatureCommand } from './commands/feature.js';
|
|
5
|
+
export { ModelCommand } from './commands/model.js';
|
|
6
|
+
export { ScreenCommand } from './commands/screen.js';
|
|
7
|
+
export { FileHelper } from './utils/file-helper.js';
|
|
8
|
+
export { FeatureHelper } from './utils/feature-helper.js';
|
|
9
|
+
export { ConfigHelper } from './utils/config-helper.js';
|
|
10
|
+
export { StringUtils } from './utils/string-utils.js';
|
|
11
|
+
export { PackageJsonHelper } from './utils/packagejson-helper.js';
|
|
12
|
+
export { BaseTemplates } from './templates/base-templates.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// React Native Architecture Generator — Public API
|
|
2
|
+
export { Architecture, ArchitectureLabels, StateManagement, Routing } from './models/config.js';
|
|
3
|
+
export { InitCommand } from './commands/init.js';
|
|
4
|
+
export { FeatureCommand } from './commands/feature.js';
|
|
5
|
+
export { ModelCommand } from './commands/model.js';
|
|
6
|
+
export { ScreenCommand } from './commands/screen.js';
|
|
7
|
+
export { FileHelper } from './utils/file-helper.js';
|
|
8
|
+
export { FeatureHelper } from './utils/feature-helper.js';
|
|
9
|
+
export { ConfigHelper } from './utils/config-helper.js';
|
|
10
|
+
export { StringUtils } from './utils/string-utils.js';
|
|
11
|
+
export { PackageJsonHelper } from './utils/packagejson-helper.js';
|
|
12
|
+
export { BaseTemplates } from './templates/base-templates.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare enum Architecture {
|
|
2
|
+
cleanArchitecture = "cleanArchitecture",
|
|
3
|
+
featureBased = "featureBased",
|
|
4
|
+
atomicDesign = "atomicDesign",
|
|
5
|
+
mvvm = "mvvm"
|
|
6
|
+
}
|
|
7
|
+
export declare enum StateManagement {
|
|
8
|
+
redux = "redux",
|
|
9
|
+
zustand = "zustand",
|
|
10
|
+
context = "context"
|
|
11
|
+
}
|
|
12
|
+
export declare enum Routing {
|
|
13
|
+
reactNavigation = "reactNavigation",
|
|
14
|
+
expoRouter = "expoRouter"
|
|
15
|
+
}
|
|
16
|
+
export interface GeneratorConfig {
|
|
17
|
+
architecture: Architecture;
|
|
18
|
+
stateManagement: StateManagement;
|
|
19
|
+
routing: Routing;
|
|
20
|
+
localization: boolean;
|
|
21
|
+
firebase: boolean;
|
|
22
|
+
tests: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Friendly display names for architecture choices.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ArchitectureLabels: Record<Architecture, string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export var Architecture;
|
|
2
|
+
(function (Architecture) {
|
|
3
|
+
Architecture["cleanArchitecture"] = "cleanArchitecture";
|
|
4
|
+
Architecture["featureBased"] = "featureBased";
|
|
5
|
+
Architecture["atomicDesign"] = "atomicDesign";
|
|
6
|
+
Architecture["mvvm"] = "mvvm";
|
|
7
|
+
})(Architecture || (Architecture = {}));
|
|
8
|
+
export var StateManagement;
|
|
9
|
+
(function (StateManagement) {
|
|
10
|
+
StateManagement["redux"] = "redux";
|
|
11
|
+
StateManagement["zustand"] = "zustand";
|
|
12
|
+
StateManagement["context"] = "context";
|
|
13
|
+
})(StateManagement || (StateManagement = {}));
|
|
14
|
+
export var Routing;
|
|
15
|
+
(function (Routing) {
|
|
16
|
+
Routing["reactNavigation"] = "reactNavigation";
|
|
17
|
+
Routing["expoRouter"] = "expoRouter";
|
|
18
|
+
})(Routing || (Routing = {}));
|
|
19
|
+
/**
|
|
20
|
+
* Friendly display names for architecture choices.
|
|
21
|
+
*/
|
|
22
|
+
export const ArchitectureLabels = {
|
|
23
|
+
[Architecture.cleanArchitecture]: '🏛️ Clean Architecture (Domain → Data → Presentation)',
|
|
24
|
+
[Architecture.featureBased]: '📦 Feature-Based (Lightweight, flat structure)',
|
|
25
|
+
[Architecture.atomicDesign]: '⚛️ Atomic Design + Feature (Atoms → Molecules → Organisms)',
|
|
26
|
+
[Architecture.mvvm]: '🧩 MVVM with Hooks (Model → ViewModel → View)',
|
|
27
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { GeneratorConfig } from '../models/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Template generators for all base files in the React Native Clean Architecture project.
|
|
4
|
+
*/
|
|
5
|
+
export declare class BaseTemplates {
|
|
6
|
+
static appEntryContent(config: GeneratorConfig): string;
|
|
7
|
+
static apiClientContent(): string;
|
|
8
|
+
static failuresContent(): string;
|
|
9
|
+
static themeContent(): string;
|
|
10
|
+
static themeContextContent(): string;
|
|
11
|
+
static navigationContent(config: GeneratorConfig): string;
|
|
12
|
+
static storeContent(config: GeneratorConfig): string;
|
|
13
|
+
static constantsContent(): string;
|
|
14
|
+
static gitignoreContent(): string;
|
|
15
|
+
static sampleTestContent(): string;
|
|
16
|
+
static i18nConfigContent(): string;
|
|
17
|
+
static localeEnContent(): string;
|
|
18
|
+
}
|