micro-generate 1.0.1
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/README.md +104 -0
- package/dist/cli/generators/feature-generator.d.ts +2 -0
- package/dist/cli/generators/feature-generator.js +164 -0
- package/dist/cli/generators/feature-generator.js.map +1 -0
- package/dist/cli/generators/graphql-generator.d.ts +2 -0
- package/dist/cli/generators/graphql-generator.js +117 -0
- package/dist/cli/generators/graphql-generator.js.map +1 -0
- package/dist/cli/generators/init-generator.d.ts +2 -0
- package/dist/cli/generators/init-generator.js +400 -0
- package/dist/cli/generators/init-generator.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +87 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/types.d.ts +10 -0
- package/dist/cli/utils/types.js +2 -0
- package/dist/cli/utils/types.js.map +1 -0
- package/dist/config/database.d.ts +2 -0
- package/dist/config/database.js +27 -0
- package/dist/config/database.js.map +1 -0
- package/dist/config/env.d.ts +12 -0
- package/dist/config/env.js +17 -0
- package/dist/config/env.js.map +1 -0
- package/dist/core/MicroserviceServer.d.ts +24 -0
- package/dist/core/MicroserviceServer.js +85 -0
- package/dist/core/MicroserviceServer.js.map +1 -0
- package/dist/database/redis.d.ts +31 -0
- package/dist/database/redis.js +115 -0
- package/dist/database/redis.js.map +1 -0
- package/dist/features/hello/resolvers.d.ts +5 -0
- package/dist/features/hello/resolvers.js +6 -0
- package/dist/features/hello/resolvers.js.map +1 -0
- package/dist/features/hello/typeDefs.d.ts +1 -0
- package/dist/features/hello/typeDefs.js +6 -0
- package/dist/features/hello/typeDefs.js.map +1 -0
- package/dist/features/index.d.ts +6 -0
- package/dist/features/index.js +5 -0
- package/dist/features/index.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +43 -0
- package/dist/server.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +21 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 🚀 Microservice Generator (`micro-generate`)
|
|
2
|
+
|
|
3
|
+
**`micro-generate`** es un CLI potente y ligero para crear microservicios listos para producción con **Node.js, TypeScript, GraphQL (Apollo Server), MongoDB y Redis**.
|
|
4
|
+
|
|
5
|
+
Olvídate del boilerplate. Genera infraestructuras completas, features modulares, queries y mutaciones en segundos con una arquitectura escalable y las mejores prácticas integradas.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Instalación y Uso
|
|
10
|
+
|
|
11
|
+
No necesitas instalarlo globalmente. Recomendamos usar `npx` para ejecutar siempre la última versión:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx micro-generate <comando>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🛠️ Comandos Disponibles
|
|
20
|
+
|
|
21
|
+
### 1. Inicializar un Nuevo Proyecto
|
|
22
|
+
Crea la estructura base del microservicio, configurando TypeScript, Docker, Variables de Entorno y el Servidor.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx micro-generate init
|
|
26
|
+
```
|
|
27
|
+
* **Interactivo:** Te preguntará el nombre del proyecto y si deseas habilitar MongoDB y/o Redis.
|
|
28
|
+
* **Resultado:** Un proyecto completo con `src/core`, `src/config`, y todo listo para correr `npm run dev`.
|
|
29
|
+
|
|
30
|
+
### 2. Crear una Nueva Feature
|
|
31
|
+
Genera un módulo de dominio completo (Vertical Slice Architecture).
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx micro-generate feature
|
|
35
|
+
```
|
|
36
|
+
* **Te pedirá:** Nombre del feature (ej: `users`, `orders`).
|
|
37
|
+
* **Genera:** Carpeta en `src/features/<nombre>` con:
|
|
38
|
+
* `typeDefs.ts`: Esquemas GraphQL.
|
|
39
|
+
* `resolvers.ts`: Controladores.
|
|
40
|
+
* `service.ts`: Lógica de negocio.
|
|
41
|
+
* `model.ts`: Modelo Mongoose (si Mongo está activo).
|
|
42
|
+
* **Automático:** Registra la feature en el `index.ts` principal.
|
|
43
|
+
|
|
44
|
+
### 3. Agregar una Query (Consulta)
|
|
45
|
+
Agrega una nueva consulta GraphQL a una feature existente sin tocar el código manualmente.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx micro-generate query
|
|
49
|
+
```
|
|
50
|
+
* **Flujo:** Seleccionas la feature de una lista -> Escribes el nombre -> Defines el tipo de retorno.
|
|
51
|
+
* **Magia:** Inyecta automáticamente la definición en `typeDefs` y el esqueleto de la función en `resolvers`.
|
|
52
|
+
|
|
53
|
+
### 4. Agregar una Mutation (Mutación)
|
|
54
|
+
Agrega una nueva mutación para modificar datos.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx micro-generate mutation
|
|
58
|
+
```
|
|
59
|
+
* Similar a `query`, ideal para operaciones de creación, actualización o eliminación (CRUD).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🔥 Características Principales
|
|
64
|
+
|
|
65
|
+
* **⚡ Stack Moderno**: Node.js 20+, TypeScript 5, Apollo Server 4.
|
|
66
|
+
* **🧠 GraphQL Modular**: Arquitectura basada en features autotencidos.
|
|
67
|
+
* **🛡️ Type-Safety**: Validación de variables de entorno con `Zod`.
|
|
68
|
+
* **🗄️ Bases de Datos**:
|
|
69
|
+
* **MongoDB** (Mongoose) con conexión robusta.
|
|
70
|
+
* **Redis** para caché y pub/sub (cliente inteligente).
|
|
71
|
+
* **📝 Logging**: Implementación de alto rendimiento con **Pino** (JSON logs).
|
|
72
|
+
* **🛑 Graceful Shutdown**: Manejo correcto de señales SIGINT/SIGTERM para evitar conexiones colgadas.
|
|
73
|
+
* **🎮 Playground Control**: Habilita/deshabilita el Apollo Sandbox vía variables de entorno.
|
|
74
|
+
|
|
75
|
+
## 📂 Estructura Generada
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
my-service/
|
|
79
|
+
├── src/
|
|
80
|
+
│ ├── config/ # Configuración de entorno y DBs
|
|
81
|
+
│ ├── core/ # Servidor central (MicroserviceServer)
|
|
82
|
+
│ ├── features/ # Tu lógica de negocio aquí
|
|
83
|
+
│ │ ├── users/ # Ejemplo de Feature
|
|
84
|
+
│ │ │ ├── typeDefs.ts
|
|
85
|
+
│ │ │ ├── resolvers.ts
|
|
86
|
+
│ │ │ └── service.ts
|
|
87
|
+
│ │ └── index.ts # Auto-registro
|
|
88
|
+
│ ├── utils/ # Loggers y helpers
|
|
89
|
+
│ └── index.ts # Punto de entrada
|
|
90
|
+
├── .env # Variables de entorno
|
|
91
|
+
├── package.json
|
|
92
|
+
└── tsconfig.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 🤝 Contribuyendo
|
|
96
|
+
|
|
97
|
+
1. Haz un Fork.
|
|
98
|
+
2. Crea tu rama de feature (`git checkout -b feature/amazing-feature`).
|
|
99
|
+
3. Commit a tus cambios (`git commit -m 'feat: Add amazing feature'`).
|
|
100
|
+
4. Push a la rama (`git push origin feature/amazing-feature`).
|
|
101
|
+
5. Abre un Pull Request.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
Generado con ❤️ por `micro-generate`.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
export const generateFeature = async (options) => {
|
|
5
|
+
const { name, useMongo = true, useRedis = true } = options;
|
|
6
|
+
const featurePath = path.join(process.cwd(), 'src', 'features', name);
|
|
7
|
+
if (await fs.pathExists(featurePath)) {
|
|
8
|
+
console.error(chalk.red(`Feature ${name} already exists.`));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
await fs.ensureDir(featurePath);
|
|
12
|
+
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
13
|
+
const pascalName = capitalize(name);
|
|
14
|
+
// TypeDefs
|
|
15
|
+
const typeDefsContent = `export const typeDefs = \`#graphql
|
|
16
|
+
type ${pascalName} {
|
|
17
|
+
id: ID!
|
|
18
|
+
name: String
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ${pascalName}Response {
|
|
22
|
+
success: Boolean!
|
|
23
|
+
message: String!
|
|
24
|
+
data: ${pascalName}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type Query {
|
|
28
|
+
${name}s: [${pascalName}]
|
|
29
|
+
${name}(id: ID!): ${pascalName}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Mutation {
|
|
33
|
+
create${pascalName}(name: String!): ${pascalName}Response
|
|
34
|
+
}
|
|
35
|
+
\`;`;
|
|
36
|
+
// Model (Only if Mongo is enabled)
|
|
37
|
+
if (useMongo) {
|
|
38
|
+
const modelContent = `import mongoose, { Schema, Document } from 'mongoose';
|
|
39
|
+
|
|
40
|
+
export interface I${pascalName} extends Document {
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ${pascalName}Schema: Schema = new Schema({
|
|
45
|
+
name: { type: String, required: true },
|
|
46
|
+
}, { timestamps: true, collection: '${pascalName}' });
|
|
47
|
+
|
|
48
|
+
export const ${pascalName}Model = mongoose.model<I${pascalName}>('${pascalName}', ${pascalName}Schema);`;
|
|
49
|
+
// Save model in src/db/mongo/
|
|
50
|
+
const mongoPath = path.join(process.cwd(), 'src', 'db', 'mongo');
|
|
51
|
+
if (await fs.pathExists(mongoPath)) {
|
|
52
|
+
await fs.writeFile(path.join(mongoPath, `${name}.model.ts`), modelContent);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.warn(chalk.yellow(`Warning: src/db/mongo does not exist. Saving model in feature folder.`));
|
|
56
|
+
await fs.writeFile(path.join(featurePath, 'model.ts'), modelContent);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Service
|
|
60
|
+
let serviceImports = '';
|
|
61
|
+
let serviceClassBody = '';
|
|
62
|
+
if (useMongo) {
|
|
63
|
+
// Import from centralized location: ../../db/mongo/NAME.model.js
|
|
64
|
+
// If it was saved locally (fallback), import from ./model.js
|
|
65
|
+
const mongoPath = path.join(process.cwd(), 'src', 'db', 'mongo');
|
|
66
|
+
if (await fs.pathExists(mongoPath)) {
|
|
67
|
+
serviceImports += `import { ${pascalName}Model, I${pascalName} } from '../../db/mongo/${name}.model.js';\n`;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
serviceImports += `import { ${pascalName}Model, I${pascalName} } from './model.js';\n`;
|
|
71
|
+
}
|
|
72
|
+
serviceClassBody += `
|
|
73
|
+
async getAll(): Promise<I${pascalName}[]> {
|
|
74
|
+
return ${pascalName}Model.find();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async create(name: string): Promise<I${pascalName}> {
|
|
78
|
+
return ${pascalName}Model.create({ name });
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// In-memory or Redis-only fallback for types
|
|
84
|
+
serviceClassBody += `
|
|
85
|
+
async getAll(): Promise<any[]> {
|
|
86
|
+
return [{ id: '1', name: 'Test (No Mongo)' }];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async create(name: string): Promise<any> {
|
|
90
|
+
return { id: Math.random().toString(), name };
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
if (useRedis) {
|
|
95
|
+
// Assuming client.ts and redis.ts are at src/db/redis/ relative to src/features/FEATURE/service.ts
|
|
96
|
+
serviceImports += `import { getRedisClient } from '../../db/redis/client.js';\n`;
|
|
97
|
+
serviceImports += `import { createRedisRepository } from '../../db/redis/redis.js';\n`;
|
|
98
|
+
// Add repository property to class
|
|
99
|
+
serviceClassBody = `
|
|
100
|
+
private redis = createRedisRepository(getRedisClient, '${name}:');
|
|
101
|
+
` + serviceClassBody;
|
|
102
|
+
serviceClassBody += `
|
|
103
|
+
async getCache(key: string): Promise<any> {
|
|
104
|
+
return this.redis.find(key);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async setCache(key: string, value: string): Promise<any> {
|
|
108
|
+
return this.redis.saveTTL(key, value, 3600);
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
const serviceContent = `${serviceImports}
|
|
113
|
+
export class ${pascalName}Service {${serviceClassBody}}
|
|
114
|
+
|
|
115
|
+
export const ${name}Service = new ${pascalName}Service();`;
|
|
116
|
+
// Resolvers
|
|
117
|
+
const resolversContent = `import { ${name}Service } from './service.js';
|
|
118
|
+
|
|
119
|
+
export const resolvers = {
|
|
120
|
+
Query: {
|
|
121
|
+
${name}s: () => ${name}Service.getAll(),
|
|
122
|
+
${name}: (_: any, { id }: { id: string }) => ({ id, name: 'Test' }),
|
|
123
|
+
},
|
|
124
|
+
Mutation: {
|
|
125
|
+
create${pascalName}: async (_: any, { name }: { name: string }) => {
|
|
126
|
+
const result = await ${name}Service.create(name);
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
message: '${pascalName} created successfully',
|
|
130
|
+
data: result,
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};`;
|
|
135
|
+
await fs.writeFile(path.join(featurePath, 'typeDefs.ts'), typeDefsContent);
|
|
136
|
+
await fs.writeFile(path.join(featurePath, 'resolvers.ts'), resolversContent);
|
|
137
|
+
await fs.writeFile(path.join(featurePath, 'service.ts'), serviceContent);
|
|
138
|
+
console.log(chalk.green(`Feature ${name} created successfully at ${featurePath}`));
|
|
139
|
+
// Register feature in src/features/index.ts
|
|
140
|
+
const featuresIndexPath = path.join(process.cwd(), 'src', 'features', 'index.ts');
|
|
141
|
+
if (await fs.pathExists(featuresIndexPath)) {
|
|
142
|
+
let featuresIndexContent = await fs.readFile(featuresIndexPath, 'utf-8');
|
|
143
|
+
const typeDefsImport = `import { typeDefs as ${name}TypeDefs } from './${name}/typeDefs.js';\n`;
|
|
144
|
+
const resolversImport = `import { resolvers as ${name}Resolvers } from './${name}/resolvers.js';\n`;
|
|
145
|
+
if (!featuresIndexContent.includes(`${name}/typeDefs.js`)) {
|
|
146
|
+
// Add imports
|
|
147
|
+
const lastImportIndex = featuresIndexContent.lastIndexOf('import ');
|
|
148
|
+
const insertImportPosition = lastImportIndex !== -1 ? featuresIndexContent.indexOf('\n', lastImportIndex) + 1 : 0;
|
|
149
|
+
featuresIndexContent = featuresIndexContent.slice(0, insertImportPosition) + typeDefsImport + resolversImport + featuresIndexContent.slice(insertImportPosition);
|
|
150
|
+
// Add to exports
|
|
151
|
+
featuresIndexContent = featuresIndexContent.replace('export const typeDefs = [', `export const typeDefs = [${name}TypeDefs, `);
|
|
152
|
+
featuresIndexContent = featuresIndexContent.replace('export const resolvers = [', `export const resolvers = [${name}Resolvers, `);
|
|
153
|
+
await fs.writeFile(featuresIndexPath, featuresIndexContent);
|
|
154
|
+
console.log(chalk.blue(`Feature ${name} registered in src/features/index.ts`));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log(chalk.yellow(`Feature ${name} already registered in src/features/index.ts`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
console.warn(chalk.yellow('Remember to register the feature in src/features/index.ts (indexes file not found)'));
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=feature-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-generator.js","sourceRoot":"","sources":["../../../src/cli/generators/feature-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
export const generateQuery = async () => {
|
|
6
|
+
await generateGraphQLOperation('query');
|
|
7
|
+
};
|
|
8
|
+
export const generateMutation = async () => {
|
|
9
|
+
await generateGraphQLOperation('mutation');
|
|
10
|
+
};
|
|
11
|
+
const generateGraphQLOperation = async (type) => {
|
|
12
|
+
try {
|
|
13
|
+
// 1. Get available features
|
|
14
|
+
const featuresPath = path.join(process.cwd(), 'src', 'features');
|
|
15
|
+
if (!await fs.pathExists(featuresPath)) {
|
|
16
|
+
console.error(chalk.red('❌ src/features directory not found. Are you in the root of the project?'));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const files = await fs.readdir(featuresPath);
|
|
20
|
+
const features = files.filter(f => {
|
|
21
|
+
const fullPath = path.join(featuresPath, f);
|
|
22
|
+
return fs.statSync(fullPath).isDirectory();
|
|
23
|
+
});
|
|
24
|
+
if (features.length === 0) {
|
|
25
|
+
console.error(chalk.red('❌ No features found. Generate a feature first using generate:feature'));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// 2. Prompt user
|
|
29
|
+
const answers = await inquirer.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: 'rawlist',
|
|
32
|
+
name: 'feature',
|
|
33
|
+
message: 'Select a feature:',
|
|
34
|
+
choices: features.map(f => ({ name: f, value: f }))
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'input',
|
|
38
|
+
name: 'name',
|
|
39
|
+
message: `${type === 'query' ? 'Query' : 'Mutation'} name:`,
|
|
40
|
+
validate: (input) => input ? true : 'Name is required'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'input',
|
|
44
|
+
name: 'returnType',
|
|
45
|
+
message: 'Return type:',
|
|
46
|
+
default: 'String'
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
const { feature, name, returnType } = answers;
|
|
50
|
+
if (!feature) {
|
|
51
|
+
console.error(chalk.red('❌ No feature selected.'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const featurePath = path.join(featuresPath, feature);
|
|
55
|
+
const typeDefsPath = path.join(featurePath, 'typeDefs.ts');
|
|
56
|
+
const resolversPath = path.join(featurePath, 'resolvers.ts');
|
|
57
|
+
if (!await fs.pathExists(typeDefsPath) || !await fs.pathExists(resolversPath)) {
|
|
58
|
+
console.error(chalk.red(`❌ typeDefs.ts or resolvers.ts not found in ${featurePath}`));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// 3. Update typeDefs.ts
|
|
62
|
+
let typeDefsContent = await fs.readFile(typeDefsPath, 'utf-8');
|
|
63
|
+
const opType = type === 'query' ? 'Query' : 'Mutation';
|
|
64
|
+
const fieldDefinition = ` ${name}: ${returnType}\n`;
|
|
65
|
+
// Check if "extend type Query/Mutation" exists
|
|
66
|
+
const typeExtensionRegex = new RegExp(`extend\\s+type\\s+${opType}\\s*{`);
|
|
67
|
+
if (typeDefsContent.match(typeExtensionRegex)) {
|
|
68
|
+
// Insert into existing extension
|
|
69
|
+
typeDefsContent = typeDefsContent.replace(typeExtensionRegex, (match) => `${match}\n${fieldDefinition}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Create new extension, insert before the end of the template string
|
|
73
|
+
// Assuming format: export const typeDefs = `#graphql ... `;
|
|
74
|
+
const lastBacktickIndex = typeDefsContent.lastIndexOf('`');
|
|
75
|
+
if (lastBacktickIndex !== -1) {
|
|
76
|
+
const newExtension = `\n extend type ${opType} {\n${fieldDefinition} }\n`;
|
|
77
|
+
typeDefsContent = typeDefsContent.slice(0, lastBacktickIndex) + newExtension + typeDefsContent.slice(lastBacktickIndex);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.error(chalk.red('❌ Could not parse typeDefs.ts structure.'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await fs.writeFile(typeDefsPath, typeDefsContent);
|
|
85
|
+
// 4. Update resolvers.ts
|
|
86
|
+
let resolversContent = await fs.readFile(resolversPath, 'utf-8');
|
|
87
|
+
// Check if keys "Query:" or "Mutation:" exist
|
|
88
|
+
const resolverKeyRegex = new RegExp(`${opType}\\s*:\\s*{`);
|
|
89
|
+
const resolverFunction = ` ${name}: async (_: any, args: any) => {
|
|
90
|
+
// TODO: Implement logic
|
|
91
|
+
return null;
|
|
92
|
+
},`;
|
|
93
|
+
if (resolversContent.match(resolverKeyRegex)) {
|
|
94
|
+
// Insert into existing resolver object
|
|
95
|
+
resolversContent = resolversContent.replace(resolverKeyRegex, (match) => `${match}\n${resolverFunction}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Create new key in export const resolvers = { ... }
|
|
99
|
+
// We look for the export declaration and append the new key
|
|
100
|
+
const exportRegex = /export\s+const\s+resolvers\s*=\s*{/;
|
|
101
|
+
if (resolversContent.match(exportRegex)) {
|
|
102
|
+
const newResolverBlock = `\n ${opType}: {\n${resolverFunction}\n },`;
|
|
103
|
+
resolversContent = resolversContent.replace(exportRegex, (match) => `${match}${newResolverBlock}`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.error(chalk.red('❌ Could not parse resolvers.ts structure.'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
await fs.writeFile(resolversPath, resolversContent);
|
|
111
|
+
console.log(chalk.green(`✅ ${opType} '${name}' added to feature '${feature}' successfully!`));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error(chalk.red('An error occurred during generation:'), error);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=graphql-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql-generator.js","sourceRoot":"","sources":["../../../src/cli/generators/graphql-generator.ts"],"names":[],"mappings":"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"}
|