jarvis-arch-hexagonal-gen 1.0.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/.create-resource.json +6 -0
- package/README.md +74 -0
- package/bin/create-resource.js +10 -0
- package/dist/.create-resource.json +6 -0
- package/dist/cli/commands/generate.js +33 -0
- package/dist/cli/commands/init.js +112 -0
- package/dist/cli/commands/list.js +51 -0
- package/dist/cli/index.js +27 -0
- package/dist/core/generator.js +121 -0
- package/dist/core/hook-runner.js +14 -0
- package/dist/core/parser.js +19 -0
- package/dist/core/plugin-manager.js +64 -0
- package/dist/plugins/bundles/crud/hooks.js +1 -0
- package/dist/plugins/bundles/crud/manifest.json +6 -0
- package/dist/plugins/bundles/resource/hooks.js +1 -0
- package/dist/plugins/bundles/resource/manifest.json +6 -0
- package/dist/plugins/controller/hooks.js +20 -0
- package/dist/plugins/controller/manifest.json +14 -0
- package/dist/plugins/controller/templates/controller.ejs +66 -0
- package/dist/plugins/dto/hooks.js +11 -0
- package/dist/plugins/dto/manifest.json +19 -0
- package/dist/plugins/dto/templates/create.ejs +11 -0
- package/dist/plugins/dto/templates/update.ejs +11 -0
- package/dist/plugins/repository/hooks.js +1 -0
- package/dist/plugins/repository/manifest.json +15 -0
- package/dist/plugins/repository/templates/interface.ejs +9 -0
- package/dist/plugins/repository/templates/repository.ejs +38 -0
- package/dist/plugins/routes/hooks.js +1 -0
- package/dist/plugins/routes/manifest.json +11 -0
- package/dist/plugins/routes/templates/routes.ejs +34 -0
- package/dist/plugins/swagger/hooks.js +1 -0
- package/dist/plugins/swagger/manifest.json +11 -0
- package/dist/plugins/swagger/templates/swagger.ejs +123 -0
- package/dist/plugins/usecases/hooks.js +1 -0
- package/dist/plugins/usecases/manifest.json +27 -0
- package/dist/plugins/usecases/templates/create.ejs +11 -0
- package/dist/plugins/usecases/templates/delete.ejs +11 -0
- package/dist/plugins/usecases/templates/find-all.ejs +10 -0
- package/dist/plugins/usecases/templates/find-by-id.ejs +12 -0
- package/dist/plugins/usecases/templates/update.ejs +12 -0
- package/dist/utils/config.js +15 -0
- package/dist/utils/file-system.js +1 -0
- package/dist/utils/logger.js +7 -0
- package/package.json +45 -0
- package/scripts/copy-plugins.js +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
folder/
|
|
2
|
+
├── package.json
|
|
3
|
+
├── tsconfig.json
|
|
4
|
+
├── README.md
|
|
5
|
+
├── bin/
|
|
6
|
+
│ └── create-resource.js # Ponto de entrada da CLI
|
|
7
|
+
├── src/
|
|
8
|
+
│ ├── core/
|
|
9
|
+
│ │ ├── Generator.ts # Motor principal (renderiza templates + executa hooks)
|
|
10
|
+
│ │ ├── PluginManager.ts # Carrega plugins e resolve dependências
|
|
11
|
+
│ │ ├── Parser.ts # Converte "Product" → metadados enriquecidos
|
|
12
|
+
│ │ └── HookRunner.ts # Executa hooks (pré/pós geração)
|
|
13
|
+
│ ├── cli/
|
|
14
|
+
│ │ ├── index.ts # Comandos da CLI (generate, init, list)
|
|
15
|
+
│ │ └── commands/
|
|
16
|
+
│ │ ├── generate.ts
|
|
17
|
+
│ │ ├── init.ts
|
|
18
|
+
│ │ └── list.ts
|
|
19
|
+
│ ├── plugins/ # Plugins nativos (cada um com manifesto + templates + hooks)
|
|
20
|
+
│ │ ├── dto/
|
|
21
|
+
│ │ │ ├── manifest.json
|
|
22
|
+
│ │ │ ├── templates/
|
|
23
|
+
│ │ │ │ ├── create.ejs
|
|
24
|
+
│ │ │ │ └── update.ejs
|
|
25
|
+
│ │ │ └── hooks.ts
|
|
26
|
+
│ │ ├── controller/
|
|
27
|
+
│ │ │ ├── manifest.json
|
|
28
|
+
│ │ │ ├── templates/
|
|
29
|
+
│ │ │ │ └── controller.ejs
|
|
30
|
+
│ │ │ └── hooks.ts
|
|
31
|
+
│ │ ├── repository/
|
|
32
|
+
│ │ │ ├── manifest.json
|
|
33
|
+
│ │ │ ├── templates/
|
|
34
|
+
│ │ │ │ ├── interface.ejs
|
|
35
|
+
│ │ │ │ └── repository.ejs
|
|
36
|
+
│ │ │ └── hooks.ts
|
|
37
|
+
│ │ ├── usecases/
|
|
38
|
+
│ │ │ ├── manifest.json
|
|
39
|
+
│ │ │ ├── templates/
|
|
40
|
+
│ │ │ │ ├── create.ejs
|
|
41
|
+
│ │ │ │ ├── update.ejs
|
|
42
|
+
│ │ │ │ ├── delete.ejs
|
|
43
|
+
│ │ │ │ ├── find-by-id.ejs
|
|
44
|
+
│ │ │ │ └── find-all.ejs
|
|
45
|
+
│ │ │ └── hooks.ts
|
|
46
|
+
│ │ ├── routes/
|
|
47
|
+
│ │ │ ├── manifest.json
|
|
48
|
+
│ │ │ ├── templates/
|
|
49
|
+
│ │ │ │ └── routes.ejs
|
|
50
|
+
│ │ │ └── hooks.ts
|
|
51
|
+
│ │ ├── tests/
|
|
52
|
+
│ │ │ ├── manifest.json
|
|
53
|
+
│ │ │ ├── templates/
|
|
54
|
+
│ │ │ │ ├── usecase.spec.ejs
|
|
55
|
+
│ │ │ │ └── integration.ejs
|
|
56
|
+
│ │ │ └── hooks.ts
|
|
57
|
+
│ │ ├── swagger/
|
|
58
|
+
│ │ │ ├── manifest.json
|
|
59
|
+
│ │ │ ├── templates/
|
|
60
|
+
│ │ │ │ └── swagger.ejs
|
|
61
|
+
│ │ │ └── hooks.ts
|
|
62
|
+
│ │ └── bundles/ # Combinações de plugins (ex: resource, crud)
|
|
63
|
+
│ │ ├── resource/
|
|
64
|
+
│ │ │ ├── manifest.json # extends: [dto, controller, repository, routes, tests]
|
|
65
|
+
│ │ │ └── hooks.ts
|
|
66
|
+
│ │ └── crud/
|
|
67
|
+
│ │ ├── manifest.json # extends: [resource, swagger]
|
|
68
|
+
│ │ └── hooks.ts
|
|
69
|
+
│ └── utils/
|
|
70
|
+
│ ├── file-system.ts
|
|
71
|
+
│ └── logger.ts
|
|
72
|
+
└── templates/ # Templates globais (opcionais, podem ser usados por vários plugins)
|
|
73
|
+
└── shared/
|
|
74
|
+
└── header.ejs
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateCommand = generateCommand;
|
|
7
|
+
// src/cli/commands/generate.ts
|
|
8
|
+
const generator_1 = require("../../core/generator");
|
|
9
|
+
const parser_1 = require("../../core/parser");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const config_1 = require("../../utils/config");
|
|
12
|
+
async function generateCommand(plugin, entity, options) {
|
|
13
|
+
let columns = [];
|
|
14
|
+
if (options.fields) {
|
|
15
|
+
columns = options.fields.split(',').map((field) => {
|
|
16
|
+
const [name, type] = field.split(':');
|
|
17
|
+
return { name, type: type || 'string' };
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const metadata = (0, parser_1.parseEntity)(entity, { columns });
|
|
21
|
+
// Lê configuração
|
|
22
|
+
const config = (0, config_1.loadConfig)();
|
|
23
|
+
const pluginsDir = config.pluginsDir || 'src/plugins';
|
|
24
|
+
const generator = new generator_1.Generator(path_1.default.join(process.cwd(), pluginsDir));
|
|
25
|
+
try {
|
|
26
|
+
await generator.generate(plugin, metadata, options);
|
|
27
|
+
console.log('✨ Geração concluída!');
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error('❌ Erro:', error.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initCommand = initCommand;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
/**
|
|
12
|
+
* Inicializa um novo projeto com a estrutura mínima necessária para usar o gerador.
|
|
13
|
+
* Cria pastas, arquivos de exemplo e instala dependências recomendadas.
|
|
14
|
+
*/
|
|
15
|
+
async function initCommand() {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const folders = [
|
|
18
|
+
'src/modules',
|
|
19
|
+
'src/shared',
|
|
20
|
+
'tests/unit',
|
|
21
|
+
'tests/integration',
|
|
22
|
+
'plugins',
|
|
23
|
+
];
|
|
24
|
+
console.log(chalk_1.default.bold.blue('\n🚀 Inicializando projeto com create-api...\n'));
|
|
25
|
+
// 1. Cria estrutura de pastas
|
|
26
|
+
for (const folder of folders) {
|
|
27
|
+
const fullPath = path_1.default.join(cwd, folder);
|
|
28
|
+
if (!fs_extra_1.default.existsSync(fullPath)) {
|
|
29
|
+
fs_extra_1.default.ensureDirSync(fullPath);
|
|
30
|
+
console.log(chalk_1.default.green(`✅ Criado: ${folder}`));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(chalk_1.default.gray(`ℹ️ Já existe: ${folder}`));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 2. Cria um arquivo de configuração .create-api.json
|
|
37
|
+
const configPath = path_1.default.join(cwd, '.create-api.json');
|
|
38
|
+
if (!fs_extra_1.default.existsSync(configPath)) {
|
|
39
|
+
const defaultConfig = {
|
|
40
|
+
pluginsDir: 'plugins',
|
|
41
|
+
templatesDir: 'templates',
|
|
42
|
+
outputDir: 'src/modules',
|
|
43
|
+
defaultPlugin: 'resource',
|
|
44
|
+
};
|
|
45
|
+
fs_extra_1.default.writeJsonSync(configPath, defaultConfig, { spaces: 2 });
|
|
46
|
+
console.log(chalk_1.default.green('✅ Criado: .create-api.json'));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log(chalk_1.default.gray('ℹ️ .create-api.json já existe.'));
|
|
50
|
+
}
|
|
51
|
+
// 3. Cria um exemplo de plugin (opcional, mas didático)
|
|
52
|
+
const examplePluginDir = path_1.default.join(cwd, 'plugins', 'example');
|
|
53
|
+
if (!fs_extra_1.default.existsSync(examplePluginDir)) {
|
|
54
|
+
fs_extra_1.default.ensureDirSync(examplePluginDir);
|
|
55
|
+
fs_extra_1.default.ensureDirSync(path_1.default.join(examplePluginDir, 'templates'));
|
|
56
|
+
// Manifesto exemplo
|
|
57
|
+
const manifestExample = {
|
|
58
|
+
name: 'example',
|
|
59
|
+
description: 'Exemplo de plugin para demonstrar a estrutura',
|
|
60
|
+
dependencies: [],
|
|
61
|
+
files: [
|
|
62
|
+
{
|
|
63
|
+
template: 'hello.ejs',
|
|
64
|
+
output: 'src/modules/<%= route %>/hello.txt',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
fs_extra_1.default.writeJsonSync(path_1.default.join(examplePluginDir, 'manifest.json'), manifestExample, { spaces: 2 });
|
|
69
|
+
// Template exemplo
|
|
70
|
+
const templateContent = 'Olá, <%= entity %>! Este é um template de exemplo.';
|
|
71
|
+
fs_extra_1.default.writeFileSync(path_1.default.join(examplePluginDir, 'templates', 'hello.ejs'), templateContent);
|
|
72
|
+
console.log(chalk_1.default.green('✅ Criado plugin de exemplo: plugins/example'));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(chalk_1.default.gray('ℹ️ Plugin de exemplo já existe.'));
|
|
76
|
+
}
|
|
77
|
+
// 4. (Opcional) Instala dependências básicas
|
|
78
|
+
console.log(chalk_1.default.yellow('\n📦 Instalando dependências recomendadas...'));
|
|
79
|
+
try {
|
|
80
|
+
(0, child_process_1.execSync)('npm install -D typescript @types/node ts-node ejs fs-extra chalk text-table commander', {
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
cwd,
|
|
83
|
+
});
|
|
84
|
+
console.log(chalk_1.default.green('✅ Dependências instaladas.'));
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.warn(chalk_1.default.yellow('⚠️ Falha ao instalar dependências. Faça manualmente:'));
|
|
88
|
+
console.warn(' npm install -D typescript @types/node ts-node ejs fs-extra chalk text-table commander');
|
|
89
|
+
}
|
|
90
|
+
// 5. Cria um script de exemplo no package.json
|
|
91
|
+
const pkgPath = path_1.default.join(cwd, 'package.json');
|
|
92
|
+
if (fs_extra_1.default.existsSync(pkgPath)) {
|
|
93
|
+
const pkg = fs_extra_1.default.readJsonSync(pkgPath);
|
|
94
|
+
if (!pkg.scripts)
|
|
95
|
+
pkg.scripts = {};
|
|
96
|
+
if (!pkg.scripts['generate']) {
|
|
97
|
+
pkg.scripts['generate'] = 'create-api generate';
|
|
98
|
+
fs_extra_1.default.writeJsonSync(pkgPath, pkg, { spaces: 2 });
|
|
99
|
+
console.log(chalk_1.default.green('✅ Adicionado script "generate" ao package.json'));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.warn(chalk_1.default.yellow('⚠️ package.json não encontrado. Crie um com "npm init -y".'));
|
|
104
|
+
}
|
|
105
|
+
console.log(chalk_1.default.bold.green('\n✨ Projeto inicializado com sucesso!\n'));
|
|
106
|
+
console.log(chalk_1.default.cyan('Próximos passos:'));
|
|
107
|
+
console.log(' 1. Crie seus próprios plugins em plugins/');
|
|
108
|
+
console.log(' 2. Gere artefatos com:');
|
|
109
|
+
console.log(` ${chalk_1.default.white('create-api generate resource Product')}`);
|
|
110
|
+
console.log(' 3. Ou use:');
|
|
111
|
+
console.log(` ${chalk_1.default.white('npm run generate resource Product')}\n`);
|
|
112
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listCommand = listCommand;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const text_table_1 = __importDefault(require("text-table"));
|
|
11
|
+
/**
|
|
12
|
+
* Lista todos os plugins disponíveis, exibindo nome, descrição e dependências.
|
|
13
|
+
*/
|
|
14
|
+
async function listCommand() {
|
|
15
|
+
const pluginsDir = path_1.default.join(__dirname, '../../../plugins');
|
|
16
|
+
if (!fs_extra_1.default.existsSync(pluginsDir)) {
|
|
17
|
+
console.error(chalk_1.default.red('❌ Diretório de plugins não encontrado.'));
|
|
18
|
+
console.error(chalk_1.default.yellow('Execute `create-api init` para criar a estrutura.'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const pluginFolders = fs_extra_1.default.readdirSync(pluginsDir).filter((item) => {
|
|
22
|
+
const stat = fs_extra_1.default.statSync(path_1.default.join(pluginsDir, item));
|
|
23
|
+
return stat.isDirectory() && !item.startsWith('.');
|
|
24
|
+
});
|
|
25
|
+
if (pluginFolders.length === 0) {
|
|
26
|
+
console.log(chalk_1.default.yellow('⚠️ Nenhum plugin encontrado.'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(chalk_1.default.bold.blue('\n📦 Plugins disponíveis:\n'));
|
|
30
|
+
const rows = [
|
|
31
|
+
[chalk_1.default.bold('Nome'), chalk_1.default.bold('Descrição'), chalk_1.default.bold('Dependências')],
|
|
32
|
+
];
|
|
33
|
+
for (const folder of pluginFolders) {
|
|
34
|
+
const manifestPath = path_1.default.join(pluginsDir, folder, 'manifest.json');
|
|
35
|
+
if (!fs_extra_1.default.existsSync(manifestPath)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const manifest = fs_extra_1.default.readJsonSync(manifestPath);
|
|
40
|
+
const name = chalk_1.default.green(folder);
|
|
41
|
+
const description = manifest.description || '';
|
|
42
|
+
const deps = (manifest.dependencies || []).join(', ') || '—';
|
|
43
|
+
rows.push([name, description, deps]);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Ignora manifestos inválidos
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log((0, text_table_1.default)(rows, { align: ['l', 'l', 'l'] }));
|
|
50
|
+
console.log(`\n💡 Use: ${chalk_1.default.cyan('create-api generate <plugin> <Entity>')}`);
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const generate_1 = require("./commands/generate");
|
|
6
|
+
const init_1 = require("./commands/init");
|
|
7
|
+
const list_1 = require("./commands/list");
|
|
8
|
+
const program = new commander_1.Command();
|
|
9
|
+
program
|
|
10
|
+
.name('create-api')
|
|
11
|
+
.description('Gerador de código baseado em plugins')
|
|
12
|
+
.version('1.0.0');
|
|
13
|
+
program
|
|
14
|
+
.command('generate <plugin> <entity>')
|
|
15
|
+
.description('Gera artefatos usando um plugin')
|
|
16
|
+
.option('--swagger', 'Inclui documentação Swagger')
|
|
17
|
+
.option('--fields <fields>', 'Define campos (ex: name:string age:number)')
|
|
18
|
+
.action(generate_1.generateCommand);
|
|
19
|
+
program
|
|
20
|
+
.command('init')
|
|
21
|
+
.description('Inicializa um projeto com exemplos')
|
|
22
|
+
.action(init_1.initCommand);
|
|
23
|
+
program
|
|
24
|
+
.command('list')
|
|
25
|
+
.description('Lista todos os plugins disponíveis')
|
|
26
|
+
.action(list_1.listCommand);
|
|
27
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Generator = void 0;
|
|
7
|
+
// src/core/Generator.ts
|
|
8
|
+
const ejs_1 = __importDefault(require("ejs"));
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const plugin_manager_1 = require("./plugin-manager");
|
|
12
|
+
const hook_runner_1 = require("./hook-runner");
|
|
13
|
+
class Generator {
|
|
14
|
+
constructor(pluginsDir) {
|
|
15
|
+
const defaultPluginsDir = path_1.default.join(process.cwd(), 'src', 'plugins');
|
|
16
|
+
this.pluginManager = new plugin_manager_1.PluginManager(pluginsDir || defaultPluginsDir);
|
|
17
|
+
this.hookRunner = new hook_runner_1.HookRunner();
|
|
18
|
+
}
|
|
19
|
+
async generate(pluginName, metadata, options = {}) {
|
|
20
|
+
const plugin = this.pluginManager.load(pluginName);
|
|
21
|
+
if (!plugin)
|
|
22
|
+
throw new Error(`Plugin "${pluginName}" não encontrado`);
|
|
23
|
+
// Executa dependências primeiro (se houver)
|
|
24
|
+
if (plugin.dependencies && plugin.dependencies.length > 0) {
|
|
25
|
+
for (const dep of plugin.dependencies) {
|
|
26
|
+
await this.generate(dep, metadata, options);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Executa hooks pré (do plugin atual)
|
|
30
|
+
if (plugin.hooks?.pre) {
|
|
31
|
+
await this.hookRunner.run(plugin.hooks.pre, metadata, options);
|
|
32
|
+
}
|
|
33
|
+
// Processa os arquivos do plugin
|
|
34
|
+
for (const file of plugin.files || []) {
|
|
35
|
+
if (file.condition && !this.evaluateCondition(file.condition, options)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const outputPath = ejs_1.default.render(file.output, { ...metadata, ...options });
|
|
39
|
+
const fullOutputPath = path_1.default.join(process.cwd(), outputPath);
|
|
40
|
+
const templatePath = path_1.default.join(plugin.templatesDir, file.template);
|
|
41
|
+
if (!fs_extra_1.default.existsSync(templatePath)) {
|
|
42
|
+
console.warn(`⚠️ Template não encontrado: ${templatePath}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const templateContent = await fs_extra_1.default.readFile(templatePath, 'utf-8');
|
|
46
|
+
const rendered = ejs_1.default.render(templateContent, { ...metadata, ...options });
|
|
47
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(fullOutputPath));
|
|
48
|
+
await fs_extra_1.default.writeFile(fullOutputPath, rendered);
|
|
49
|
+
console.log(`✅ Gerado: ${outputPath}`);
|
|
50
|
+
}
|
|
51
|
+
// Executa hooks pós
|
|
52
|
+
if (plugin.hooks?.post) {
|
|
53
|
+
await this.hookRunner.run(plugin.hooks.post, metadata, options);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Avalia se um arquivo deve ser gerado com base na condição definida
|
|
58
|
+
* no manifesto e nas opções fornecidas pelo usuário.
|
|
59
|
+
*
|
|
60
|
+
* Atualmente são suportados os seguintes operadores:
|
|
61
|
+
*
|
|
62
|
+
* - equals:
|
|
63
|
+
* Compara o valor de uma opção com um valor esperado.
|
|
64
|
+
*
|
|
65
|
+
* Exemplo:
|
|
66
|
+
* {
|
|
67
|
+
* option: "swagger",
|
|
68
|
+
* equals: true
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* options:
|
|
72
|
+
* {
|
|
73
|
+
* swagger: true
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* Resultado: true
|
|
77
|
+
*
|
|
78
|
+
* ------------------------------------------------------------
|
|
79
|
+
*
|
|
80
|
+
* - in:
|
|
81
|
+
* Verifica se o valor da opção pertence a uma lista de valores.
|
|
82
|
+
*
|
|
83
|
+
* Exemplo:
|
|
84
|
+
* {
|
|
85
|
+
* option: "orm",
|
|
86
|
+
* in: ["typeorm", "prisma"]
|
|
87
|
+
* }
|
|
88
|
+
*
|
|
89
|
+
* options:
|
|
90
|
+
* {
|
|
91
|
+
* orm: "typeorm"
|
|
92
|
+
* }
|
|
93
|
+
*
|
|
94
|
+
* Resultado: true
|
|
95
|
+
*
|
|
96
|
+
* ------------------------------------------------------------
|
|
97
|
+
*
|
|
98
|
+
* O retorno possui a seguinte semântica:
|
|
99
|
+
*
|
|
100
|
+
* true -> A condição foi satisfeita e o arquivo será gerado.
|
|
101
|
+
* false -> A condição não foi satisfeita e o arquivo será ignorado.
|
|
102
|
+
*
|
|
103
|
+
* Recomenda-se lançar uma exceção quando a condição possuir um formato
|
|
104
|
+
* desconhecido, evitando que manifestos inválidos passem despercebidos.
|
|
105
|
+
*
|
|
106
|
+
* @param condition Condição declarada no manifesto.
|
|
107
|
+
* @param options Opções informadas pelo usuário durante a execução da CLI.
|
|
108
|
+
* @returns `true` quando o arquivo deve ser gerado; caso contrário, `false`.
|
|
109
|
+
* @throws Error Quando o formato da condição não é suportado.
|
|
110
|
+
*/
|
|
111
|
+
evaluateCondition(condition, options) {
|
|
112
|
+
if ("equals" in condition) {
|
|
113
|
+
return options[condition.option] === condition.equals;
|
|
114
|
+
}
|
|
115
|
+
if ("in" in condition) {
|
|
116
|
+
return condition.in.includes(options[condition.option]);
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Unsupported condition: ${JSON.stringify(condition)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.Generator = Generator;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HookRunner = void 0;
|
|
4
|
+
class HookRunner {
|
|
5
|
+
async run(hookFunction, metadata, options) {
|
|
6
|
+
// O hookFunction pode ser o nome da função exportada no hooks.ts
|
|
7
|
+
// ou um caminho para um script.
|
|
8
|
+
// Para simplificar, assumimos que está no mesmo plugin.
|
|
9
|
+
// Implementação real pode usar require dinâmico.
|
|
10
|
+
console.log(`⏳ Executando hook: ${hookFunction}`);
|
|
11
|
+
// ...
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.HookRunner = HookRunner;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseEntity = parseEntity;
|
|
4
|
+
function parseEntity(entityName, options = {}) {
|
|
5
|
+
const entity = entityName.charAt(0).toUpperCase() + entityName.slice(1);
|
|
6
|
+
const entityLower = entity.toLowerCase();
|
|
7
|
+
const plural = entity + 's';
|
|
8
|
+
const pluralLower = plural.toLowerCase();
|
|
9
|
+
const route = pluralLower;
|
|
10
|
+
const table = `tb_${pluralLower}`;
|
|
11
|
+
// Colunas podem vir das opções ou de um schema
|
|
12
|
+
const columns = options.columns || [
|
|
13
|
+
{ name: 'id', type: 'number', primary: true },
|
|
14
|
+
{ name: 'name', type: 'string' },
|
|
15
|
+
{ name: 'createdAt', type: 'Date' },
|
|
16
|
+
{ name: 'updatedAt', type: 'Date' },
|
|
17
|
+
];
|
|
18
|
+
return { entity, entityName: entity, entityLower, plural, pluralLower, route, table, columns };
|
|
19
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PluginManager = void 0;
|
|
7
|
+
// src/core/PluginManager.ts
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
class PluginManager {
|
|
11
|
+
constructor(pluginsDir) {
|
|
12
|
+
this.cache = new Map();
|
|
13
|
+
this.pluginsDir = pluginsDir;
|
|
14
|
+
}
|
|
15
|
+
load(pluginName) {
|
|
16
|
+
if (this.cache.has(pluginName))
|
|
17
|
+
return this.cache.get(pluginName);
|
|
18
|
+
const foundPath = this.findPluginPath(pluginName);
|
|
19
|
+
if (!foundPath) {
|
|
20
|
+
throw new Error(`Manifest não encontrado para o plugin "${pluginName}"`);
|
|
21
|
+
}
|
|
22
|
+
const manifestPath = path_1.default.join(foundPath, 'manifest.json');
|
|
23
|
+
const manifest = fs_extra_1.default.readJsonSync(manifestPath);
|
|
24
|
+
const templatesDir = path_1.default.join(foundPath, 'templates');
|
|
25
|
+
let hooks = null;
|
|
26
|
+
const hooksPath = path_1.default.join(foundPath, 'hooks.ts');
|
|
27
|
+
if (fs_extra_1.default.existsSync(hooksPath)) {
|
|
28
|
+
try {
|
|
29
|
+
hooks = require(hooksPath);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// ignora
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const plugin = {
|
|
36
|
+
...manifest,
|
|
37
|
+
templatesDir,
|
|
38
|
+
hooks,
|
|
39
|
+
path: foundPath,
|
|
40
|
+
};
|
|
41
|
+
this.cache.set(pluginName, plugin);
|
|
42
|
+
return plugin;
|
|
43
|
+
}
|
|
44
|
+
findPluginPath(pluginName) {
|
|
45
|
+
const search = (dir) => {
|
|
46
|
+
const items = fs_extra_1.default.readdirSync(dir);
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
const fullPath = path_1.default.join(dir, item);
|
|
49
|
+
const stat = fs_extra_1.default.statSync(fullPath);
|
|
50
|
+
if (stat.isDirectory()) {
|
|
51
|
+
if (item === pluginName && fs_extra_1.default.existsSync(path_1.default.join(fullPath, 'manifest.json'))) {
|
|
52
|
+
return fullPath;
|
|
53
|
+
}
|
|
54
|
+
const found = search(fullPath);
|
|
55
|
+
if (found)
|
|
56
|
+
return found;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
};
|
|
61
|
+
return search(this.pluginsDir);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.PluginManager = PluginManager;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerRoutes = registerRoutes;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function registerRoutes(metadata, options) {
|
|
10
|
+
const routesFile = path_1.default.join(process.cwd(), 'src/routes/index.ts');
|
|
11
|
+
if (await fs_extra_1.default.pathExists(routesFile)) {
|
|
12
|
+
const content = await fs_extra_1.default.readFile(routesFile, 'utf-8');
|
|
13
|
+
const newImport = `import { ${metadata.entity}Controller } from '../modules/${metadata.route}/${metadata.entity}Controller';\n`;
|
|
14
|
+
const newRoute = `router.use('/${metadata.route}', ${metadata.entity}Controller);\n`;
|
|
15
|
+
if (!content.includes(newImport)) {
|
|
16
|
+
await fs_extra_1.default.appendFile(routesFile, `\n${newImport}${newRoute}`);
|
|
17
|
+
console.log(`📝 Registrada rota em routes/index.ts`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "controller",
|
|
3
|
+
"description": "Gera um controller CRUD",
|
|
4
|
+
"dependencies": ["dto"],
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"template": "controller.ejs",
|
|
8
|
+
"output": "src/adapters/inbound/controllers/<%= entity %>Controller.ts"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"hooks": {
|
|
12
|
+
"post": "registerRoutes"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import { Create<%= entity %>UseCase } from '../usecases/create-<%= entityLower %>.usecase';
|
|
3
|
+
import { Update<%= entity %>UseCase } from '../usecases/update-<%= entityLower %>.usecase';
|
|
4
|
+
import { Delete<%= entity %>UseCase } from '../usecases/delete-<%= entityLower %>.usecase';
|
|
5
|
+
import { Find<%= entity %>ByIdUseCase } from '../usecases/find-<%= entityLower %>-by-id.usecase';
|
|
6
|
+
import { FindAll<%= plural %>UseCase } from '../usecases/find-all-<%= pluralLower %>.usecase';
|
|
7
|
+
|
|
8
|
+
export class <%= entity %>Controller {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly createUseCase: Create<%= entity %>UseCase,
|
|
11
|
+
private readonly updateUseCase: Update<%= entity %>UseCase,
|
|
12
|
+
private readonly deleteUseCase: Delete<%= entity %>UseCase,
|
|
13
|
+
private readonly findByIdUseCase: Find<%= entity %>ByIdUseCase,
|
|
14
|
+
private readonly findAllUseCase: FindAll<%= plural %>UseCase
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
async create(req: Request, res: Response): Promise<Response> {
|
|
18
|
+
try {
|
|
19
|
+
const data = req.body;
|
|
20
|
+
const result = await this.createUseCase.execute(data);
|
|
21
|
+
return res.status(201).json(result);
|
|
22
|
+
} catch (error: any) {
|
|
23
|
+
return res.status(400).json({ error: error.message });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async update(req: Request, res: Response): Promise<Response> {
|
|
28
|
+
try {
|
|
29
|
+
const id = Number(req.params.id);
|
|
30
|
+
const data = req.body;
|
|
31
|
+
const result = await this.updateUseCase.execute(id, data);
|
|
32
|
+
return res.json(result);
|
|
33
|
+
} catch (error: any) {
|
|
34
|
+
return res.status(400).json({ error: error.message });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async delete(req: Request, res: Response): Promise<Response> {
|
|
39
|
+
try {
|
|
40
|
+
const id = Number(req.params.id);
|
|
41
|
+
await this.deleteUseCase.execute(id);
|
|
42
|
+
return res.status(204).send();
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
return res.status(400).json({ error: error.message });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async findById(req: Request, res: Response): Promise<Response> {
|
|
49
|
+
try {
|
|
50
|
+
const id = Number(req.params.id);
|
|
51
|
+
const result = await this.findByIdUseCase.execute(id);
|
|
52
|
+
return res.json(result);
|
|
53
|
+
} catch (error: any) {
|
|
54
|
+
return res.status(404).json({ error: error.message });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async findAll(req: Request, res: Response): Promise<Response> {
|
|
59
|
+
try {
|
|
60
|
+
const result = await this.findAllUseCase.execute();
|
|
61
|
+
return res.json(result);
|
|
62
|
+
} catch (error: any) {
|
|
63
|
+
return res.status(500).json({ error: error.message });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preGenerate = preGenerate;
|
|
4
|
+
exports.postGenerate = postGenerate;
|
|
5
|
+
function preGenerate(metadata, options) {
|
|
6
|
+
console.log(`📦 Preparando DTOs para ${metadata.entity}`);
|
|
7
|
+
}
|
|
8
|
+
function postGenerate(metadata, options) {
|
|
9
|
+
// Pode atualizar um arquivo central de exportação
|
|
10
|
+
// ou instalar dependências, etc.
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dto",
|
|
3
|
+
"description": "Gera DTOs de criação e atualização",
|
|
4
|
+
"dependencies": [],
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"template": "create.ejs",
|
|
8
|
+
"output": "src/core/dtos/create-<%= route %>.dto.ts"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"template": "update.ejs",
|
|
12
|
+
"output": "src/core/dtos/update-<%= route %>.dto.ts"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"hooks": {
|
|
16
|
+
"pre": "preGenerate",
|
|
17
|
+
"post": "postGenerate"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repository",
|
|
3
|
+
"description": "Gera interface e implementação do repositório para uma entidade",
|
|
4
|
+
"dependencies": [],
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"template": "interface.ejs",
|
|
8
|
+
"output": "src/core/ports/outbound/repositories/I<%= entity %>Repository.ts"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"template": "repository.ejs",
|
|
12
|
+
"output": "src/adapters/outbound/persistence/typeorm/repositories/<%= entity %>Repository.ts"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { <%= entity %> } from '../entities/<%= entityLower %>.entity';
|
|
2
|
+
|
|
3
|
+
export interface I<%= entity %>Repository {
|
|
4
|
+
create(data: Omit<<%= entity %>, 'id'>): Promise<<%= entity %>>;
|
|
5
|
+
update(id: number, data: Partial<<%= entity %>>): Promise<<%= entity %>>;
|
|
6
|
+
delete(id: number): Promise<void>;
|
|
7
|
+
findById(id: number): Promise<<%= entity %> | null>;
|
|
8
|
+
findAll(): Promise<<%= entity %>[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { <%= entity %> } from '../entities/<%= entityLower %>.entity';
|
|
2
|
+
import { I<%= entity %>Repository } from './I<%= entity %>Repository';
|
|
3
|
+
|
|
4
|
+
export class <%= entity %>Repository implements I<%= entity %>Repository {
|
|
5
|
+
private items: <%= entity %>[] = [];
|
|
6
|
+
private currentId = 1;
|
|
7
|
+
|
|
8
|
+
async create(data: Omit<<%= entity %>, 'id'>): Promise<<%= entity %>> {
|
|
9
|
+
const newItem: <%= entity %> = {
|
|
10
|
+
id: this.currentId++,
|
|
11
|
+
...data,
|
|
12
|
+
} as <%= entity %>;
|
|
13
|
+
this.items.push(newItem);
|
|
14
|
+
return newItem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async update(id: number, data: Partial<<%= entity %>>): Promise<<%= entity %>> {
|
|
18
|
+
const index = this.items.findIndex(item => item.id === id);
|
|
19
|
+
if (index === -1) throw new Error('<%= entity %> not found');
|
|
20
|
+
const updated = { ...this.items[index], ...data };
|
|
21
|
+
this.items[index] = updated;
|
|
22
|
+
return updated;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async delete(id: number): Promise<void> {
|
|
26
|
+
const index = this.items.findIndex(item => item.id === id);
|
|
27
|
+
if (index === -1) throw new Error('<%= entity %> not found');
|
|
28
|
+
this.items.splice(index, 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async findById(id: number): Promise<<%= entity %> | null> {
|
|
32
|
+
return this.items.find(item => item.id === id) || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async findAll(): Promise<<%= entity %>[]> {
|
|
36
|
+
return this.items;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "routes",
|
|
3
|
+
"description": "Gera rotas Express para a entidade, com injeção de dependências",
|
|
4
|
+
"dependencies": ["controller"],
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"template": "routes.ejs",
|
|
8
|
+
"output": "src/adapters/inbound/routes/<%= route %>/<%= route %>.routes.ts"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { <%= entity %>Controller } from './<%= entityLower %>.controller';
|
|
3
|
+
import { <%= entity %>Repository } from './repositories/<%= entityLower %>.repository';
|
|
4
|
+
import { Create<%= entity %>UseCase } from './usecases/create-<%= entityLower %>.usecase';
|
|
5
|
+
import { Update<%= entity %>UseCase } from './usecases/update-<%= entityLower %>.usecase';
|
|
6
|
+
import { Delete<%= entity %>UseCase } from './usecases/delete-<%= entityLower %>.usecase';
|
|
7
|
+
import { Find<%= entity %>ByIdUseCase } from './usecases/find-<%= entityLower %>-by-id.usecase';
|
|
8
|
+
import { FindAll<%= plural %>UseCase } from './usecases/find-all-<%= pluralLower %>.usecase';
|
|
9
|
+
|
|
10
|
+
const router = Router();
|
|
11
|
+
|
|
12
|
+
// Instâncias
|
|
13
|
+
const repository = new <%= entity %>Repository();
|
|
14
|
+
const createUseCase = new Create<%= entity %>UseCase(repository);
|
|
15
|
+
const updateUseCase = new Update<%= entity %>UseCase(repository);
|
|
16
|
+
const deleteUseCase = new Delete<%= entity %>UseCase(repository);
|
|
17
|
+
const findByIdUseCase = new Find<%= entity %>ByIdUseCase(repository);
|
|
18
|
+
const findAllUseCase = new FindAll<%= plural %>UseCase(repository);
|
|
19
|
+
|
|
20
|
+
const controller = new <%= entity %>Controller(
|
|
21
|
+
createUseCase,
|
|
22
|
+
updateUseCase,
|
|
23
|
+
deleteUseCase,
|
|
24
|
+
findByIdUseCase,
|
|
25
|
+
findAllUseCase
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
router.post('/', (req, res) => controller.create(req, res));
|
|
29
|
+
router.put('/:id', (req, res) => controller.update(req, res));
|
|
30
|
+
router.delete('/:id', (req, res) => controller.delete(req, res));
|
|
31
|
+
router.get('/:id', (req, res) => controller.findById(req, res));
|
|
32
|
+
router.get('/', (req, res) => controller.findAll(req, res));
|
|
33
|
+
|
|
34
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
openapi: 3.0.0
|
|
2
|
+
info:
|
|
3
|
+
title: API - <%= plural %>
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Documentação da API para gerenciamento de <%= pluralLower %>
|
|
6
|
+
|
|
7
|
+
paths:
|
|
8
|
+
/api/<%= route %>:
|
|
9
|
+
post:
|
|
10
|
+
summary: Cria um novo <%= entityLower %>
|
|
11
|
+
tags:
|
|
12
|
+
- <%= plural %>
|
|
13
|
+
requestBody:
|
|
14
|
+
required: true
|
|
15
|
+
content:
|
|
16
|
+
application/json:
|
|
17
|
+
schema:
|
|
18
|
+
$ref: '#/components/schemas/<%= entity %>Input'
|
|
19
|
+
responses:
|
|
20
|
+
'201':
|
|
21
|
+
description: Criado com sucesso
|
|
22
|
+
content:
|
|
23
|
+
application/json:
|
|
24
|
+
schema:
|
|
25
|
+
$ref: '#/components/schemas/<%= entity %>'
|
|
26
|
+
'400':
|
|
27
|
+
description: Dados inválidos
|
|
28
|
+
get:
|
|
29
|
+
summary: Lista todos os <%= pluralLower %>
|
|
30
|
+
tags:
|
|
31
|
+
- <%= plural %>
|
|
32
|
+
responses:
|
|
33
|
+
'200':
|
|
34
|
+
description: Lista de <%= pluralLower %>
|
|
35
|
+
content:
|
|
36
|
+
application/json:
|
|
37
|
+
schema:
|
|
38
|
+
type: array
|
|
39
|
+
items:
|
|
40
|
+
$ref: '#/components/schemas/<%= entity %>'
|
|
41
|
+
|
|
42
|
+
/api/<%= route %>/{id}:
|
|
43
|
+
get:
|
|
44
|
+
summary: Busca um <%= entityLower %> por ID
|
|
45
|
+
tags:
|
|
46
|
+
- <%= plural %>
|
|
47
|
+
parameters:
|
|
48
|
+
- name: id
|
|
49
|
+
in: path
|
|
50
|
+
required: true
|
|
51
|
+
schema:
|
|
52
|
+
type: integer
|
|
53
|
+
responses:
|
|
54
|
+
'200':
|
|
55
|
+
description: <%= entity %> encontrado
|
|
56
|
+
content:
|
|
57
|
+
application/json:
|
|
58
|
+
schema:
|
|
59
|
+
$ref: '#/components/schemas/<%= entity %>'
|
|
60
|
+
'404':
|
|
61
|
+
description: <%= entity %> não encontrado
|
|
62
|
+
put:
|
|
63
|
+
summary: Atualiza um <%= entityLower %>
|
|
64
|
+
tags:
|
|
65
|
+
- <%= plural %>
|
|
66
|
+
parameters:
|
|
67
|
+
- name: id
|
|
68
|
+
in: path
|
|
69
|
+
required: true
|
|
70
|
+
schema:
|
|
71
|
+
type: integer
|
|
72
|
+
requestBody:
|
|
73
|
+
required: true
|
|
74
|
+
content:
|
|
75
|
+
application/json:
|
|
76
|
+
schema:
|
|
77
|
+
$ref: '#/components/schemas/<%= entity %>Input'
|
|
78
|
+
responses:
|
|
79
|
+
'200':
|
|
80
|
+
description: Atualizado com sucesso
|
|
81
|
+
content:
|
|
82
|
+
application/json:
|
|
83
|
+
schema:
|
|
84
|
+
$ref: '#/components/schemas/<%= entity %>'
|
|
85
|
+
'404':
|
|
86
|
+
description: <%= entity %> não encontrado
|
|
87
|
+
'400':
|
|
88
|
+
description: Dados inválidos
|
|
89
|
+
delete:
|
|
90
|
+
summary: Deleta um <%= entityLower %>
|
|
91
|
+
tags:
|
|
92
|
+
- <%= plural %>
|
|
93
|
+
parameters:
|
|
94
|
+
- name: id
|
|
95
|
+
in: path
|
|
96
|
+
required: true
|
|
97
|
+
schema:
|
|
98
|
+
type: integer
|
|
99
|
+
responses:
|
|
100
|
+
'204':
|
|
101
|
+
description: Deletado com sucesso
|
|
102
|
+
'404':
|
|
103
|
+
description: <%= entity %> não encontrado
|
|
104
|
+
|
|
105
|
+
components:
|
|
106
|
+
schemas:
|
|
107
|
+
<%= entity %>:
|
|
108
|
+
type: object
|
|
109
|
+
properties:
|
|
110
|
+
<% columns.forEach(c => { %> <%= c.name %>:
|
|
111
|
+
type: <%= c.type === 'number' ? 'number' : 'string' %>
|
|
112
|
+
<% if (c.primary) { %> readOnly: true
|
|
113
|
+
<% } %><% }) %> required:
|
|
114
|
+
<% columns.filter(c => !c.primary).forEach(c => { %> - <%= c.name %>
|
|
115
|
+
<% }) %>
|
|
116
|
+
<%= entity %>Input:
|
|
117
|
+
type: object
|
|
118
|
+
properties:
|
|
119
|
+
<% columns.filter(c => !c.primary).forEach(c => { %> <%= c.name %>:
|
|
120
|
+
type: <%= c.type === 'number' ? 'number' : 'string' %>
|
|
121
|
+
<% }) %> required:
|
|
122
|
+
<% columns.filter(c => !c.primary).forEach(c => { %> - <%= c.name %>
|
|
123
|
+
<% }) %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "usecases",
|
|
3
|
+
"description": "Gera todos os use cases para a entidade (CRUD)",
|
|
4
|
+
"dependencies": ["repository"],
|
|
5
|
+
"files": [
|
|
6
|
+
{
|
|
7
|
+
"template": "create.ejs",
|
|
8
|
+
"output": "src/core/usecases/<%= route %>/create-<%= entityLower %>.usecase.ts"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"template": "update.ejs",
|
|
12
|
+
"output": "src/core/usecases/<%= route %>/update-<%= entityLower %>.usecase.ts"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"template": "delete.ejs",
|
|
16
|
+
"output": "src/core/usecases/<%= route %>/delete-<%= entityLower %>.usecase.ts"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"template": "find-by-id.ejs",
|
|
20
|
+
"output": "src/core/usecases/<%= route %>/find-<%= entityLower %>-by-id.usecase.ts"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"template": "find-all.ejs",
|
|
24
|
+
"output": "src/core/usecases/<%= route %>/find-all-<%= pluralLower %>.usecase.ts"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { <%= entity %> } from '../entities/<%= entityLower %>.entity';
|
|
2
|
+
import { I<%= entity %>Repository } from '../repositories/I<%= entity %>Repository';
|
|
3
|
+
|
|
4
|
+
export class Create<%= entity %>UseCase {
|
|
5
|
+
constructor(private readonly repository: I<%= entity %>Repository) {}
|
|
6
|
+
|
|
7
|
+
async execute(data: Omit<<%= entity %>, 'id'>): Promise<<%= entity %>> {
|
|
8
|
+
// Aqui pode adicionar validações, regras de negócio, etc.
|
|
9
|
+
return this.repository.create(data);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { I<%= entity %>Repository } from '../repositories/I<%= entity %>Repository';
|
|
2
|
+
|
|
3
|
+
export class Delete<%= entity %>UseCase {
|
|
4
|
+
constructor(private readonly repository: I<%= entity %>Repository) {}
|
|
5
|
+
|
|
6
|
+
async execute(id: number): Promise<void> {
|
|
7
|
+
const existing = await this.repository.findById(id);
|
|
8
|
+
if (!existing) throw new Error('<%= entity %> not found');
|
|
9
|
+
await this.repository.delete(id);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { <%= entity %> } from '../entities/<%= entityLower %>.entity';
|
|
2
|
+
import { I<%= entity %>Repository } from '../repositories/I<%= entity %>Repository';
|
|
3
|
+
|
|
4
|
+
export class FindAll<%= plural %>UseCase {
|
|
5
|
+
constructor(private readonly repository: I<%= entity %>Repository) {}
|
|
6
|
+
|
|
7
|
+
async execute(): Promise<<%= entity %>[]> {
|
|
8
|
+
return this.repository.findAll();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { <%= entity %> } from '../entities/<%= entityLower %>.entity';
|
|
2
|
+
import { I<%= entity %>Repository } from '../repositories/I<%= entity %>Repository';
|
|
3
|
+
|
|
4
|
+
export class Find<%= entity %>ByIdUseCase {
|
|
5
|
+
constructor(private readonly repository: I<%= entity %>Repository) {}
|
|
6
|
+
|
|
7
|
+
async execute(id: number): Promise<<%= entity %>> {
|
|
8
|
+
const result = await this.repository.findById(id);
|
|
9
|
+
if (!result) throw new Error('<%= entity %> not found');
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { <%= entity %> } from '../entities/<%= entityLower %>.entity';
|
|
2
|
+
import { I<%= entity %>Repository } from '../repositories/I<%= entity %>Repository';
|
|
3
|
+
|
|
4
|
+
export class Update<%= entity %>UseCase {
|
|
5
|
+
constructor(private readonly repository: I<%= entity %>Repository) {}
|
|
6
|
+
|
|
7
|
+
async execute(id: number, data: Partial<<%= entity %>>): Promise<<%= entity %>> {
|
|
8
|
+
const existing = await this.repository.findById(id);
|
|
9
|
+
if (!existing) throw new Error('<%= entity %> not found');
|
|
10
|
+
return this.repository.update(id, data);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function loadConfig() {
|
|
10
|
+
const configPath = path_1.default.join(process.cwd(), '.create-resource.json');
|
|
11
|
+
if (fs_extra_1.default.existsSync(configPath)) {
|
|
12
|
+
return fs_extra_1.default.readJsonSync(configPath);
|
|
13
|
+
}
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jarvis-arch-hexagonal-gen",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Gerador de recursos para projetos Node.js com TypeScript",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"Arquitetura Hexagonal",
|
|
7
|
+
"Gerador de Códigos"
|
|
8
|
+
],
|
|
9
|
+
"author": "Anderson Paiva",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"main": "./dist/cli/index.js",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"bin": {
|
|
14
|
+
"create-resource": "./dist/cli/index.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"generate": "ts-node src/cli/index.ts generate",
|
|
19
|
+
"build": "tsc && npm run copy-plugins",
|
|
20
|
+
"copy-plugins": "node scripts/copy-plugins.js"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chalk": "^5.6.2",
|
|
24
|
+
"commander": "^15.0.0",
|
|
25
|
+
"ejs": "^6.0.1",
|
|
26
|
+
"text-table": "^0.2.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/chalk": "^0.4.31",
|
|
30
|
+
"@types/ejs": "^3.1.5",
|
|
31
|
+
"@types/fs-extra": "^11.0.4",
|
|
32
|
+
"@types/jest": "^30.0.0",
|
|
33
|
+
"@types/node": "^26.0.1",
|
|
34
|
+
"@types/supertest": "^7.2.0",
|
|
35
|
+
"@types/text-table": "^0.2.5",
|
|
36
|
+
"copyfiles": "^2.4.1",
|
|
37
|
+
"cpx": "^1.5.0",
|
|
38
|
+
"fs-extra": "^11.3.5",
|
|
39
|
+
"jest": "^30.4.2",
|
|
40
|
+
"supertest": "^7.2.2",
|
|
41
|
+
"ts-jest": "^29.4.11",
|
|
42
|
+
"ts-node": "^10.9.2",
|
|
43
|
+
"typescript": "^6.0.3"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Ajuste o caminho de origem conforme sua estrutura:
|
|
5
|
+
// Se seus plugins estão em 'src/plugins', use:
|
|
6
|
+
const srcDir = path.join(__dirname, '..', 'src', 'plugins');
|
|
7
|
+
// Se estiverem na raiz ('plugins'), use:
|
|
8
|
+
// const srcDir = path.join(__dirname, '..', 'plugins');
|
|
9
|
+
|
|
10
|
+
const destDir = path.join(__dirname, '..', 'dist', 'plugins');
|
|
11
|
+
|
|
12
|
+
// Função recursiva para copiar apenas arquivos não-.ts
|
|
13
|
+
function copyNonTsFiles(src, dest) {
|
|
14
|
+
// Cria o diretório de destino se não existir
|
|
15
|
+
fs.ensureDirSync(dest);
|
|
16
|
+
|
|
17
|
+
const items = fs.readdirSync(src);
|
|
18
|
+
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
const srcPath = path.join(src, item);
|
|
21
|
+
const destPath = path.join(dest, item);
|
|
22
|
+
const stat = fs.statSync(srcPath);
|
|
23
|
+
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
// Recursivamente para subpastas
|
|
26
|
+
copyNonTsFiles(srcPath, destPath);
|
|
27
|
+
} else {
|
|
28
|
+
// Copia apenas se NÃO for .ts
|
|
29
|
+
if (!item.endsWith('.ts')) {
|
|
30
|
+
fs.copySync(srcPath, destPath);
|
|
31
|
+
// console.log(`Copiado: ${srcPath} -> ${destPath}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Executa a cópia (sem remover nada)
|
|
38
|
+
copyNonTsFiles(srcDir, destDir);
|
|
39
|
+
|
|
40
|
+
console.log('✅ Arquivos não-.ts copiados de src/plugins para dist/plugins');
|