@codigodoleo/wp-kit 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/.cspell.json +104 -0
- package/.editorconfig +13 -0
- package/.eslintignore +3 -0
- package/.eslintrc.json +24 -0
- package/.github/workflows/publish-npm.yml +32 -0
- package/.github/workflows/release-please.yml +25 -0
- package/.gitkeep +0 -0
- package/.husky/_/post-merge +0 -0
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/.vscode/settings.json +19 -0
- package/CHANGELOG.md +47 -0
- package/README.md +115 -0
- package/VSCODE_EXTENSIONS.md +11 -0
- package/bin/index.js +4 -0
- package/docs/HOOKS-SYSTEM.md +172 -0
- package/git-deploy.sh +74 -0
- package/index.php +17 -0
- package/lib/cli.js +15 -0
- package/lib/commands/init.js +132 -0
- package/lib/core/generator.js +261 -0
- package/lib/core/hook-manager.js +172 -0
- package/lib/core/infer-ci-capabilities.js +119 -0
- package/lib/prompts/index.js +105 -0
- package/lib/prompts/loadModulePrompts.js +27 -0
- package/lib/utils/generate-from-template.js +17 -0
- package/lib/utils/git.js +17 -0
- package/lib/utils/logger.js +28 -0
- package/modules/deploy/index.js +39 -0
- package/modules/deploy/prompts.js +27 -0
- package/modules/deploy/templates/.github/workflows/ci.yml.hbs +103 -0
- package/modules/deploy/templates/.gitlab/gitlab-ci.yml.hbs +154 -0
- package/modules/deploy/templates/bitbucket-pipelines.yml.hbs +97 -0
- package/modules/docs/index.js +3 -0
- package/modules/docs/prompts.js +8 -0
- package/modules/docs/templates/README.md.hbs +160 -0
- package/modules/docs/templates/docs/Arquitetura.md +399 -0
- package/modules/docs/templates/docs/Deploy-Pipeline.md +113 -0
- package/modules/docs/templates/docs/Desenvolvimento.md +1116 -0
- package/modules/docs/templates/docs/Getting-Started.md +493 -0
- package/modules/docs/templates/docs/Infraestrutura.md +211 -0
- package/modules/docs/templates/docs/Monitoramento.md +302 -0
- package/modules/docs/templates/docs/Sync-the-Production-DB-with-the-Staging-DB.md +8 -0
- package/modules/docs/templates/docs/Troubleshooting.md +3 -0
- package/modules/git/.commitlintrc.json +136 -0
- package/modules/git/.github/PULL_REQUEST_TEMPLATE.md +42 -0
- package/modules/git/.gitlab/merge_request_templates/default.md +42 -0
- package/modules/git/.gitmessage +29 -0
- package/modules/git/.husky/commit-msg +4 -0
- package/modules/git/.husky/pre-commit +27 -0
- package/modules/git/.lintstagedrc.json +7 -0
- package/modules/git/.vscode/commit-instructions.md +59 -0
- package/modules/git/.vscode/conventional-commits.code-snippets +62 -0
- package/modules/git/.vscode/copilot.json +39 -0
- package/modules/git/docs/CONVENTIONAL-COMMITS.md +131 -0
- package/modules/git/index.js +137 -0
- package/modules/git/prompts.js +23 -0
- package/modules/git/templates/.lando.yml.hbs +13 -0
- package/modules/git/templates/package.json.hbs +15 -0
- package/modules/git/templates/workspace.json.hbs +114 -0
- package/modules/lint/.eslintignore +36 -0
- package/modules/lint/.eslintrc.json +8 -0
- package/modules/lint/.prettierignore +36 -0
- package/modules/lint/.prettierrc.json +29 -0
- package/modules/lint/.stylelintignore +19 -0
- package/modules/lint/.stylelintrc.json +9 -0
- package/modules/lint/index.js +15 -0
- package/modules/lint/pint.json +26 -0
- package/modules/lint/prompts.js +16 -0
- package/modules/lint/templates/.lando.yml.hbs +10 -0
- package/modules/lint/templates/package.json.hbs +16 -0
- package/modules/lint/templates/workspace.json.hbs +56 -0
- package/modules/php/index.js +3 -0
- package/modules/php/prompts.js +8 -0
- package/modules/php/scripts/php-wrapper.sh +38 -0
- package/modules/php/scripts/pint-wrapper.sh +44 -0
- package/modules/php/templates/.lando.yml.hbs +11 -0
- package/modules/php/templates/composer.json.hbs +6 -0
- package/modules/php/templates/workspace.json.hbs +74 -0
- package/modules/redis/prompts.js +8 -0
- package/modules/redis/templates/.lando.yml.hbs +8 -0
- package/modules/sage/index.js +20 -0
- package/modules/sage/prompts.js +16 -0
- package/modules/sage/templates/.lando.yml.hbs +64 -0
- package/modules/sage/templates/theme/composer.json.hbs +18 -0
- package/modules/sage/templates/theme/package.json.hbs +11 -0
- package/modules/sage/templates/theme/style.css.hbs +13 -0
- package/modules/sage/templates/theme/vite.config.js.hbs +53 -0
- package/modules/sage/templates/workspace.json.hbs +67 -0
- package/modules/test-directory/assets/module-file.txt +1 -0
- package/modules/test-directory/index.js +19 -0
- package/modules/test-directory/prompts.js +8 -0
- package/modules/test-directory/test-assets/file1.txt +1 -0
- package/modules/test-directory/test-assets/file2.txt +1 -0
- package/modules/test-directory/test-assets/subfolder/config.json +4 -0
- package/package.json +54 -0
- package/release-please-config.json +17 -0
- package/server/php/php.ini +48 -0
- package/server/www/rocket.conf +283 -0
- package/templates/.editorconfig.hbs +39 -0
- package/templates/.env.hbs +48 -0
- package/templates/.gitignore.hbs +86 -0
- package/templates/.lando.yml.hbs +44 -0
- package/templates/README.md.hbs +12 -0
- package/templates/composer.json.hbs +60 -0
- package/templates/package.json.hbs +47 -0
- package/templates/server/cmd/install-wp.sh.hbs +58 -0
- package/templates/server/www/vhosts.conf.hbs +71 -0
- package/templates/workspace.json.hbs +177 -0
- package/test-copy-directory.js +43 -0
- package/test-overwrite.js +45 -0
- package/wp-config.php +190 -0
package/git-deploy.sh
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script para inicializar git, adicionar remote e fazer push forçado
|
|
4
|
+
# Uso: ./git-deploy.sh [remote-url] [branch] [commit-message]
|
|
5
|
+
|
|
6
|
+
set -e # Sai se algum comando falhar
|
|
7
|
+
|
|
8
|
+
# Configurações padrão
|
|
9
|
+
DEFAULT_REMOTE="git@gitlab.com:pipefy/wordpress/web-template.git"
|
|
10
|
+
DEFAULT_BRANCH="main"
|
|
11
|
+
DEFAULT_COMMIT_MESSAGE="Initial commit"
|
|
12
|
+
|
|
13
|
+
# Parâmetros
|
|
14
|
+
REMOTE_URL=${1:-$DEFAULT_REMOTE}
|
|
15
|
+
BRANCH=${2:-$DEFAULT_BRANCH}
|
|
16
|
+
COMMIT_MESSAGE=${3:-$DEFAULT_COMMIT_MESSAGE}
|
|
17
|
+
|
|
18
|
+
echo "🚀 Iniciando processo de deploy..."
|
|
19
|
+
echo "Remote: $REMOTE_URL"
|
|
20
|
+
echo "Branch: $BRANCH"
|
|
21
|
+
echo "Commit message: $COMMIT_MESSAGE"
|
|
22
|
+
echo ""
|
|
23
|
+
|
|
24
|
+
# Verificar se estamos em um diretório com arquivos
|
|
25
|
+
if [ ! "$(ls -A .)" ]; then
|
|
26
|
+
echo "❌ Erro: Diretório vazio. Adicione alguns arquivos antes de executar."
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Inicializar git
|
|
31
|
+
echo "📦 Inicializando repositório Git..."
|
|
32
|
+
git init
|
|
33
|
+
|
|
34
|
+
# Verificar se o remote já existe e remover se necessário
|
|
35
|
+
if git remote get-url origin &>/dev/null; then
|
|
36
|
+
echo "🔄 Removendo remote origin existente..."
|
|
37
|
+
git remote remove origin
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Adicionar remote
|
|
41
|
+
echo "🔗 Adicionando remote origin..."
|
|
42
|
+
git remote add origin "$REMOTE_URL"
|
|
43
|
+
|
|
44
|
+
# Verificar se há arquivos para adicionar
|
|
45
|
+
echo "📋 Adicionando arquivos ao stage..."
|
|
46
|
+
git add .
|
|
47
|
+
|
|
48
|
+
# Verificar se há algo para commitar
|
|
49
|
+
if git diff --cached --quiet; then
|
|
50
|
+
echo "ℹ️ Nenhuma mudança para commitar."
|
|
51
|
+
else
|
|
52
|
+
echo "💾 Criando commit..."
|
|
53
|
+
git commit -m "$COMMIT_MESSAGE"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Confirmar push forçado
|
|
57
|
+
echo ""
|
|
58
|
+
echo "⚠️ ATENÇÃO: Este comando fará um push FORÇADO para a branch '$BRANCH'."
|
|
59
|
+
echo " Isso pode sobrescrever o histórico existente no repositório remoto."
|
|
60
|
+
echo ""
|
|
61
|
+
read -p "Deseja continuar? (y/N): " -n 1 -r
|
|
62
|
+
echo ""
|
|
63
|
+
|
|
64
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
65
|
+
echo "🚀 Fazendo push forçado para $BRANCH..."
|
|
66
|
+
git push -f origin "$BRANCH"
|
|
67
|
+
echo ""
|
|
68
|
+
echo "✅ Deploy concluído com sucesso!"
|
|
69
|
+
echo "🔗 Repositório: $REMOTE_URL"
|
|
70
|
+
echo "🌿 Branch: $BRANCH"
|
|
71
|
+
else
|
|
72
|
+
echo "❌ Deploy cancelado pelo usuário."
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
package/index.php
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Front to the WordPress application. This file doesn't do anything, but loads
|
|
4
|
+
* wp-blog-header.php which does and tells WordPress to load the theme.
|
|
5
|
+
*
|
|
6
|
+
* @package WordPress
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tells WordPress to load the WordPress theme and output it.
|
|
11
|
+
*
|
|
12
|
+
* @var bool
|
|
13
|
+
*/
|
|
14
|
+
define( 'WP_USE_THEMES', true );
|
|
15
|
+
|
|
16
|
+
/** Loads the WordPress Environment and Template */
|
|
17
|
+
require __DIR__ . '/wp/wp-blog-header.php';
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { initCommand } from './commands/init.js';
|
|
3
|
+
|
|
4
|
+
const program = new Command();
|
|
5
|
+
|
|
6
|
+
export function runCli() {
|
|
7
|
+
program
|
|
8
|
+
.name('leo-wp')
|
|
9
|
+
.description('WP Kit — Scaffold/Kit WordPress com CI dinâmico (@codigodoleo)')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
|
|
12
|
+
program.addCommand(initCommand);
|
|
13
|
+
|
|
14
|
+
program.parse();
|
|
15
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { runPrompts } from '../prompts/index.js';
|
|
5
|
+
import { Generator } from '../core/generator.js';
|
|
6
|
+
import { log, color } from '../utils/logger.js';
|
|
7
|
+
import { inferCiCapabilities } from '../core/infer-ci-capabilities.js';
|
|
8
|
+
|
|
9
|
+
export const initCommand = new Command('init')
|
|
10
|
+
.option('-o, --output <dir>', 'Output directory', '__test-sandbox__/test-project')
|
|
11
|
+
.option('--defaults', 'Use default answers without prompting')
|
|
12
|
+
.option('--debug', 'Ativa modo debug para logs detalhados')
|
|
13
|
+
.action(async ({ output, defaults, debug }) => {
|
|
14
|
+
const target = path.resolve(output);
|
|
15
|
+
const { enabledModules, ...context } = await runPrompts({ defaults });
|
|
16
|
+
|
|
17
|
+
// Normalização de chaves para consistência entre módulos
|
|
18
|
+
if (context.enable_sage === true) {
|
|
19
|
+
context.useSage = true;
|
|
20
|
+
}
|
|
21
|
+
if (context.git_provider && !context.gitProvider) {
|
|
22
|
+
context.gitProvider = context.git_provider;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 🔧 Ajustes automáticos para Sage 11
|
|
26
|
+
if (context.useSage && context.sageVersion === '11') {
|
|
27
|
+
context.phpVersion = '8.3';
|
|
28
|
+
context.nodeVersion = '23';
|
|
29
|
+
context.wpCoreVersion = '7.5';
|
|
30
|
+
|
|
31
|
+
log(color.orange, '🔧 Sage 11 selected — automatically adjusting versions:');
|
|
32
|
+
log(color.orange, ' → PHP: 8.3');
|
|
33
|
+
log(color.orange, ' → Node: 23');
|
|
34
|
+
log(color.orange, ' → WP Core: 7.5');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
context.cwd = target;
|
|
38
|
+
|
|
39
|
+
// 🧠 Inferência de capacidades de CI
|
|
40
|
+
context.ci = inferCiCapabilities(context);
|
|
41
|
+
|
|
42
|
+
log(color.blue, '[ci] Capacidades inferidas:');
|
|
43
|
+
log(color.blue, ` → composer: ${context.ci.build.composer}`);
|
|
44
|
+
log(color.blue, ` → node: ${context.ci.build.node}`);
|
|
45
|
+
log(color.blue, ` → sage: ${context.ci.build.sage}`);
|
|
46
|
+
log(color.blue, ` → deploy.docker: ${context.ci.deploy.docker}`);
|
|
47
|
+
log(color.blue, ` → nodeVersion: ${context.ci.nodeVersion}`);
|
|
48
|
+
if (context.ci.theme.paths.themeRoot) {
|
|
49
|
+
log(color.blue, ` → themeRoot: ${context.ci.theme.paths.themeRoot}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const generator = new Generator(target);
|
|
53
|
+
await fs.ensureDir(target);
|
|
54
|
+
|
|
55
|
+
await generator.generateFile('.gitignore', context);
|
|
56
|
+
await generator.generateFile('README.md', context);
|
|
57
|
+
await generator.generateFile('.env', context);
|
|
58
|
+
await generator.generateFile('.editorconfig', context);
|
|
59
|
+
await generator.generateFile('server/cmd/install-wp.sh', context);
|
|
60
|
+
|
|
61
|
+
await generator.copyFile('.cspell.json');
|
|
62
|
+
|
|
63
|
+
await generator.copyFile('server/php/php.ini');
|
|
64
|
+
|
|
65
|
+
await generator.copyFile('.gitkeep', null, 'content/uploads/.gitkeep');
|
|
66
|
+
await generator.copyFile('.gitkeep', null, 'content/plugins/.gitkeep');
|
|
67
|
+
await generator.copyFile('.gitkeep', null, 'content/themes/.gitkeep');
|
|
68
|
+
await generator.copyFile('.gitkeep', null, 'content/mu-plugins/.gitkeep');
|
|
69
|
+
await generator.copyFile('.gitkeep', null, 'content/languages/.gitkeep');
|
|
70
|
+
|
|
71
|
+
await fs.chmod(path.join(target, 'content/uploads'), 0o777);
|
|
72
|
+
await fs.chmod(path.join(target, 'server/cmd/install-wp.sh'), 0o755);
|
|
73
|
+
|
|
74
|
+
await generator.generateFile('server/www/vhosts.conf', context);
|
|
75
|
+
if (context.useRocket) {
|
|
76
|
+
await generator.copyFile('server/www/rocket.conf');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await generator.copyFile('wp-config.php');
|
|
80
|
+
await generator.copyFile('index.php');
|
|
81
|
+
|
|
82
|
+
log(color.blue, `🧩 Enabled modules: ${enabledModules.join(', ')}`);
|
|
83
|
+
|
|
84
|
+
await generator.generateModularFile({
|
|
85
|
+
baseTemplate: 'package.json',
|
|
86
|
+
modules: enabledModules,
|
|
87
|
+
context,
|
|
88
|
+
format: 'json',
|
|
89
|
+
mergeStrategy: 'concat'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await generator.generateModularFile({
|
|
93
|
+
baseTemplate: 'composer.json',
|
|
94
|
+
modules: enabledModules,
|
|
95
|
+
context,
|
|
96
|
+
format: 'json',
|
|
97
|
+
mergeStrategy: 'concat'
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await generator.generateModularFile({
|
|
101
|
+
baseTemplate: '.lando.yml',
|
|
102
|
+
modules: enabledModules,
|
|
103
|
+
context,
|
|
104
|
+
mergeStrategy: 'concat'
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await generator.generateModularFile({
|
|
108
|
+
baseTemplate: 'workspace.json',
|
|
109
|
+
outputFile: `${context.projectName}.code-workspace`,
|
|
110
|
+
modules: enabledModules,
|
|
111
|
+
context,
|
|
112
|
+
format: 'json',
|
|
113
|
+
mergeStrategy: 'concat'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
for (const mod of enabledModules) {
|
|
117
|
+
const modIndexPath = path.resolve('modules', mod, 'index.js');
|
|
118
|
+
if (await fs.pathExists(modIndexPath)) {
|
|
119
|
+
const { setupModule } = await import(modIndexPath);
|
|
120
|
+
if (typeof setupModule === 'function') {
|
|
121
|
+
log(color.blue, `↪ Running setup for module: ${mod}`);
|
|
122
|
+
await setupModule(context, generator);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Executa hooks após todo o setup dos módulos
|
|
128
|
+
log(color.blue, '🔗 Executing post-setup hooks...');
|
|
129
|
+
generator.hooks.doAction('after_setup_complete', context, generator);
|
|
130
|
+
|
|
131
|
+
log(color.green, '✅ Project initialized with templates.');
|
|
132
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// lib/core/generator.js
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import handlebars from 'handlebars';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import mergeWith from 'lodash.mergewith';
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
import { log, color } from '../utils/logger.js';
|
|
9
|
+
import { HookManager } from './hook-manager.js';
|
|
10
|
+
|
|
11
|
+
export class Generator {
|
|
12
|
+
constructor(baseDir = process.cwd()) {
|
|
13
|
+
this.cwd = baseDir;
|
|
14
|
+
this.hooks = new HookManager();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async copyFile(sourcePathRelative, moduleName = null, outputName = "") {
|
|
18
|
+
const baseDir = moduleName
|
|
19
|
+
? path.resolve('modules', moduleName)
|
|
20
|
+
: path.resolve();
|
|
21
|
+
|
|
22
|
+
outputName = outputName ? outputName : sourcePathRelative;
|
|
23
|
+
|
|
24
|
+
const sourcePath = path.resolve(baseDir, sourcePathRelative);
|
|
25
|
+
const outputPath = path.join(this.cwd, outputName);
|
|
26
|
+
|
|
27
|
+
await fs.copy(sourcePath, outputPath);
|
|
28
|
+
log(color.blue, `📋 File copied: ${outputName}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Copia uma pasta inteira para o diretório de destino
|
|
33
|
+
* @param {string} sourcePathRelative - Caminho relativo da pasta de origem
|
|
34
|
+
* @param {string|null} moduleName - Nome do módulo (opcional)
|
|
35
|
+
* @param {string} outputName - Nome da pasta de destino (opcional)
|
|
36
|
+
* @param {boolean} overwrite - Se deve sobrescrever arquivos existentes (padrão: true)
|
|
37
|
+
*/
|
|
38
|
+
async copyDirectory(sourcePathRelative, moduleName = null, outputName = "", overwrite = true) {
|
|
39
|
+
const baseDir = moduleName
|
|
40
|
+
? path.resolve('modules', moduleName)
|
|
41
|
+
: path.resolve();
|
|
42
|
+
|
|
43
|
+
outputName = outputName ? outputName : sourcePathRelative;
|
|
44
|
+
|
|
45
|
+
const sourcePath = path.resolve(baseDir, sourcePathRelative);
|
|
46
|
+
const outputPath = path.join(this.cwd, outputName);
|
|
47
|
+
|
|
48
|
+
// Verifica se a pasta de origem existe
|
|
49
|
+
if (!await fs.pathExists(sourcePath)) {
|
|
50
|
+
throw new Error(`Source directory not found: ${sourcePath}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Verifica se é realmente uma pasta
|
|
54
|
+
const sourceStats = await fs.stat(sourcePath);
|
|
55
|
+
if (!sourceStats.isDirectory()) {
|
|
56
|
+
throw new Error(`Source path is not a directory: ${sourcePath}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Se a pasta de destino não existe, copia tudo
|
|
60
|
+
if (!await fs.pathExists(outputPath)) {
|
|
61
|
+
await fs.copy(sourcePath, outputPath);
|
|
62
|
+
log(color.blue, `📁 Directory copied: ${outputName}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Se a pasta de destino existe e overwrite é false, não faz nada
|
|
67
|
+
if (!overwrite) {
|
|
68
|
+
log(color.yellow, `📁 Directory already exists, skipping: ${outputName}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Se a pasta de destino existe e overwrite é true, copia arquivos individualmente
|
|
73
|
+
await this._copyDirectoryContents(sourcePath, outputPath);
|
|
74
|
+
log(color.blue, `📁 Directory contents merged: ${outputName}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Método auxiliar para copiar conteúdo de uma pasta para outra
|
|
79
|
+
* @param {string} sourcePath - Caminho absoluto da pasta de origem
|
|
80
|
+
* @param {string} outputPath - Caminho absoluto da pasta de destino
|
|
81
|
+
*/
|
|
82
|
+
async _copyDirectoryContents(sourcePath, outputPath) {
|
|
83
|
+
const items = await fs.readdir(sourcePath);
|
|
84
|
+
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
const sourceItemPath = path.join(sourcePath, item);
|
|
87
|
+
const outputItemPath = path.join(outputPath, item);
|
|
88
|
+
const stats = await fs.stat(sourceItemPath);
|
|
89
|
+
|
|
90
|
+
if (stats.isDirectory()) {
|
|
91
|
+
// Se é uma pasta, cria se não existir e copia recursivamente
|
|
92
|
+
if (!await fs.pathExists(outputItemPath)) {
|
|
93
|
+
await fs.mkdir(outputItemPath, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
await this._copyDirectoryContents(sourceItemPath, outputItemPath);
|
|
96
|
+
} else {
|
|
97
|
+
// Se é um arquivo, copia (sobrescreve se existir)
|
|
98
|
+
await fs.copy(sourceItemPath, outputItemPath);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async generateFile(templatePathRelative, context, moduleName = null, outputName = "") {
|
|
104
|
+
const baseDir = moduleName
|
|
105
|
+
? path.resolve('modules', moduleName, 'templates')
|
|
106
|
+
: path.resolve('templates');
|
|
107
|
+
|
|
108
|
+
outputName = outputName ? outputName : templatePathRelative;
|
|
109
|
+
|
|
110
|
+
const templatePath = path.resolve(baseDir, templatePathRelative + '.hbs');
|
|
111
|
+
const outputPath = path.join(this.cwd, outputName);
|
|
112
|
+
|
|
113
|
+
const content = await fs.readFile(templatePath, 'utf8');
|
|
114
|
+
const template = handlebars.compile(content);
|
|
115
|
+
const result = template(context);
|
|
116
|
+
|
|
117
|
+
await fs.outputFile(outputPath, result);
|
|
118
|
+
log(color.blue, `📄 File generated: ${outputName}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Gera um arquivo modular combinando templates base e de módulos.
|
|
123
|
+
* @param {Object} options
|
|
124
|
+
* @param {string} options.baseTemplate - Caminho do template base
|
|
125
|
+
* @param {string} options.outputFile - Nome do arquivo de saída
|
|
126
|
+
* @param {string[]} [options.modules=[]] - Lista de módulos
|
|
127
|
+
* @param {Object} [options.context={}] - Contexto para handlebars
|
|
128
|
+
* @param {'json'|'yaml'} [options.format='yaml'] - Formato do arquivo
|
|
129
|
+
* @param {'concat'|'replace'} [options.mergeStrategy='concat'] - Estratégia de merge de arrays
|
|
130
|
+
*/
|
|
131
|
+
async generateModularFile({ baseTemplate, outputFile = "", modules = [], context = {}, format = 'yaml', mergeStrategy = 'replace', debug = false }) {
|
|
132
|
+
const baseTemplatePath = path.resolve('templates', baseTemplate + '.hbs');
|
|
133
|
+
const baseRaw = await fs.readFile(baseTemplatePath, 'utf8');
|
|
134
|
+
const compiledBase = handlebars.compile(baseRaw);
|
|
135
|
+
outputFile = outputFile ? outputFile : baseTemplate;
|
|
136
|
+
let mergedOutput;
|
|
137
|
+
try {
|
|
138
|
+
mergedOutput = format === 'json'
|
|
139
|
+
? JSON.parse(compiledBase(context))
|
|
140
|
+
: yaml.load(compiledBase(context));
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (debug) {
|
|
143
|
+
log(color.red, '[DEBUG] Erro ao fazer parse do template base:');
|
|
144
|
+
log(color.red, compiledBase(context));
|
|
145
|
+
log(color.red, err);
|
|
146
|
+
}
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function customizer(objValue, srcValue) {
|
|
151
|
+
if (Array.isArray(objValue) && Array.isArray(srcValue)) {
|
|
152
|
+
return mergeStrategy === 'concat'
|
|
153
|
+
? objValue.concat(srcValue)
|
|
154
|
+
: srcValue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const mod of modules) {
|
|
159
|
+
const modPath = path.resolve('modules', mod, 'templates', path.basename(baseTemplate) + '.hbs');
|
|
160
|
+
|
|
161
|
+
if (await fs.pathExists(modPath)) {
|
|
162
|
+
const modRaw = await fs.readFile(modPath, 'utf8');
|
|
163
|
+
const compiledMod = handlebars.compile(modRaw);
|
|
164
|
+
let modParsed;
|
|
165
|
+
try {
|
|
166
|
+
modParsed = format === 'json'
|
|
167
|
+
? JSON.parse(compiledMod(context))
|
|
168
|
+
: yaml.load(compiledMod(context));
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (debug) {
|
|
171
|
+
log(color.red, `[DEBUG] Erro ao fazer parse do template do módulo ${mod}:`);
|
|
172
|
+
log(color.red, compiledMod(context));
|
|
173
|
+
log(color.red, err);
|
|
174
|
+
}
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
mergedOutput = mergeWith({}, mergedOutput, modParsed, customizer);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const finalOutput = format === 'json'
|
|
182
|
+
? JSON.stringify(mergedOutput, null, 2)
|
|
183
|
+
: yaml.dump(mergedOutput, { lineWidth: 120 });
|
|
184
|
+
|
|
185
|
+
const outputPath = path.join(this.cwd, outputFile);
|
|
186
|
+
await fs.outputFile(outputPath, finalOutput);
|
|
187
|
+
log(color.blue, `🧩 Modular file generated: ${outputFile}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async mergeWithExistingFile({ existingFile, moduleTemplatePath, context, format = 'json', moduleName = null, mergeStrategy = 'replace', debug = false }) {
|
|
191
|
+
const existingPath = path.isAbsolute(existingFile)
|
|
192
|
+
? existingFile
|
|
193
|
+
: path.resolve(this.cwd, existingFile);
|
|
194
|
+
|
|
195
|
+
const fileExists = await fs.pathExists(existingPath);
|
|
196
|
+
if (!fileExists) {
|
|
197
|
+
throw new Error(`File to merge not found: ${existingPath}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const existingRaw = await fs.readFile(existingPath, 'utf8');
|
|
201
|
+
let existingParsed;
|
|
202
|
+
try {
|
|
203
|
+
existingParsed = format === 'json' ? JSON.parse(existingRaw) : yaml.load(existingRaw);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (debug) {
|
|
206
|
+
log(color.red, '[DEBUG] Erro ao fazer parse do arquivo existente:');
|
|
207
|
+
log(color.red, existingRaw);
|
|
208
|
+
log(color.red, err);
|
|
209
|
+
}
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const moduleBase = moduleName
|
|
214
|
+
? path.resolve('modules', moduleName, 'templates')
|
|
215
|
+
: path.resolve('templates');
|
|
216
|
+
const templatePath = path.resolve(moduleBase, moduleTemplatePath + '.hbs');
|
|
217
|
+
|
|
218
|
+
const modRaw = await fs.readFile(templatePath, 'utf8');
|
|
219
|
+
const compiledMod = handlebars.compile(modRaw);
|
|
220
|
+
let modParsed;
|
|
221
|
+
try {
|
|
222
|
+
modParsed = format === 'json'
|
|
223
|
+
? JSON.parse(compiledMod(context))
|
|
224
|
+
: yaml.load(compiledMod(context));
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (debug) {
|
|
227
|
+
log(color.red, '[DEBUG] Erro ao fazer parse do template do módulo:');
|
|
228
|
+
log(color.red, compiledMod(context));
|
|
229
|
+
log(color.red, err);
|
|
230
|
+
}
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function customizer(objValue, srcValue) {
|
|
235
|
+
if (Array.isArray(objValue) && Array.isArray(srcValue)) {
|
|
236
|
+
return mergeStrategy === 'concat'
|
|
237
|
+
? objValue.concat(srcValue)
|
|
238
|
+
: srcValue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const merged = mergeWith({}, existingParsed, modParsed, customizer);
|
|
243
|
+
|
|
244
|
+
const output = format === 'json'
|
|
245
|
+
? JSON.stringify(merged, null, 2)
|
|
246
|
+
: yaml.dump(merged, { lineWidth: 120 });
|
|
247
|
+
|
|
248
|
+
await fs.outputFile(existingPath, output);
|
|
249
|
+
log(color.blue, `🔀 Merged into: ${existingPath}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static generateSalts() {
|
|
253
|
+
return [
|
|
254
|
+
'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY',
|
|
255
|
+
'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT'
|
|
256
|
+
].reduce((acc, key) => {
|
|
257
|
+
acc[key] = crypto.randomBytes(64).toString('base64');
|
|
258
|
+
return acc;
|
|
259
|
+
}, {});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { log, color } from '../utils/logger.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sistema de hooks similar ao WordPress
|
|
5
|
+
* Permite registrar callbacks que serão executados em momentos específicos
|
|
6
|
+
*/
|
|
7
|
+
export class HookManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.hooks = new Map();
|
|
10
|
+
this.currentHook = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registra uma função para ser executada em um hook específico
|
|
15
|
+
* @param {string} hookName - Nome do hook
|
|
16
|
+
* @param {Function} callback - Função a ser executada
|
|
17
|
+
* @param {number} priority - Prioridade (padrão: 10, menor número = maior prioridade)
|
|
18
|
+
* @param {number} acceptedArgs - Número de argumentos aceitos pela função
|
|
19
|
+
*/
|
|
20
|
+
addAction(hookName, callback, priority = 10, acceptedArgs = 1) {
|
|
21
|
+
if (!this.hooks.has(hookName)) {
|
|
22
|
+
this.hooks.set(hookName, []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const hook = this.hooks.get(hookName);
|
|
26
|
+
hook.push({
|
|
27
|
+
callback,
|
|
28
|
+
priority,
|
|
29
|
+
acceptedArgs
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Ordena por prioridade (menor número = maior prioridade)
|
|
33
|
+
hook.sort((a, b) => a.priority - b.priority);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registra uma função que retorna um valor (filtro)
|
|
38
|
+
* @param {string} hookName - Nome do hook
|
|
39
|
+
* @param {Function} callback - Função a ser executada
|
|
40
|
+
* @param {number} priority - Prioridade (padrão: 10)
|
|
41
|
+
* @param {number} acceptedArgs - Número de argumentos aceitos pela função
|
|
42
|
+
*/
|
|
43
|
+
addFilter(hookName, callback, priority = 10, acceptedArgs = 1) {
|
|
44
|
+
this.addAction(hookName, callback, priority, acceptedArgs);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Executa todos os callbacks registrados para um hook
|
|
49
|
+
* @param {string} hookName - Nome do hook
|
|
50
|
+
* @param {...any} args - Argumentos para passar aos callbacks
|
|
51
|
+
*/
|
|
52
|
+
doAction(hookName, ...args) {
|
|
53
|
+
if (!this.hooks.has(hookName)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const hook = this.hooks.get(hookName);
|
|
58
|
+
const previousHook = this.currentHook;
|
|
59
|
+
this.currentHook = hookName;
|
|
60
|
+
|
|
61
|
+
log(color.blue, `🔗 Executing hook: ${hookName} (${hook.length} callbacks)`);
|
|
62
|
+
|
|
63
|
+
for (const { callback, acceptedArgs } of hook) {
|
|
64
|
+
try {
|
|
65
|
+
const callbackArgs = args.slice(0, acceptedArgs);
|
|
66
|
+
callback(...callbackArgs);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
log(color.red, `❌ Error in hook ${hookName}: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.currentHook = previousHook;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Executa um filtro e retorna o valor modificado
|
|
77
|
+
* @param {string} hookName - Nome do hook
|
|
78
|
+
* @param {any} value - Valor inicial
|
|
79
|
+
* @param {...any} args - Argumentos adicionais
|
|
80
|
+
* @returns {any} Valor modificado pelos filtros
|
|
81
|
+
*/
|
|
82
|
+
applyFilters(hookName, value, ...args) {
|
|
83
|
+
if (!this.hooks.has(hookName)) {
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hook = this.hooks.get(hookName);
|
|
88
|
+
const previousHook = this.currentHook;
|
|
89
|
+
this.currentHook = hookName;
|
|
90
|
+
|
|
91
|
+
log(color.blue, `🔗 Applying filter: ${hookName} (${hook.length} callbacks)`);
|
|
92
|
+
|
|
93
|
+
let result = value;
|
|
94
|
+
for (const { callback, acceptedArgs } of hook) {
|
|
95
|
+
try {
|
|
96
|
+
const callbackArgs = [result, ...args].slice(0, acceptedArgs);
|
|
97
|
+
result = callback(...callbackArgs);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
log(color.red, `❌ Error in filter ${hookName}: ${error.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.currentHook = previousHook;
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove um callback de um hook
|
|
109
|
+
* @param {string} hookName - Nome do hook
|
|
110
|
+
* @param {Function} callback - Função a ser removida
|
|
111
|
+
* @param {number} priority - Prioridade da função
|
|
112
|
+
*/
|
|
113
|
+
removeAction(hookName, callback, priority = 10) {
|
|
114
|
+
if (!this.hooks.has(hookName)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const hook = this.hooks.get(hookName);
|
|
119
|
+
const index = hook.findIndex(
|
|
120
|
+
item => item.callback === callback && item.priority === priority
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (index !== -1) {
|
|
124
|
+
hook.splice(index, 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Remove um filtro
|
|
130
|
+
* @param {string} hookName - Nome do hook
|
|
131
|
+
* @param {Function} callback - Função a ser removida
|
|
132
|
+
* @param {number} priority - Prioridade da função
|
|
133
|
+
*/
|
|
134
|
+
removeFilter(hookName, callback, priority = 10) {
|
|
135
|
+
this.removeAction(hookName, callback, priority);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Verifica se um hook tem callbacks registrados
|
|
140
|
+
* @param {string} hookName - Nome do hook
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
hasAction(hookName) {
|
|
144
|
+
return this.hooks.has(hookName) && this.hooks.get(hookName).length > 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Verifica se um filtro tem callbacks registrados
|
|
149
|
+
* @param {string} hookName - Nome do hook
|
|
150
|
+
* @returns {boolean}
|
|
151
|
+
*/
|
|
152
|
+
hasFilter(hookName) {
|
|
153
|
+
return this.hasAction(hookName);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Lista todos os hooks registrados
|
|
158
|
+
* @returns {string[]}
|
|
159
|
+
*/
|
|
160
|
+
getHooks() {
|
|
161
|
+
return Array.from(this.hooks.keys());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Obtém informações sobre um hook específico
|
|
166
|
+
* @param {string} hookName - Nome do hook
|
|
167
|
+
* @returns {Array|null}
|
|
168
|
+
*/
|
|
169
|
+
getHook(hookName) {
|
|
170
|
+
return this.hooks.get(hookName) || null;
|
|
171
|
+
}
|
|
172
|
+
}
|