@codigodoleo/wp-kit 3.1.0 → 3.2.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/lib/commands/init.js +19 -10
- package/lib/config/versions.js +5 -5
- package/lib/prompts/index.js +24 -14
- package/lib/utils/git.js +50 -13
- package/modules/deploy/.github/workflows/deploy-docker.yml +102 -0
- package/modules/deploy/.github/workflows/deploy-ssh.yml +126 -0
- package/modules/deploy/.github/workflows/release.yml +55 -0
- package/modules/deploy/index.js +39 -0
- package/modules/deploy/prompts.js +1 -3
- package/modules/deploy/templates/.gitlab/ci/deploy-docker.yml.hbs +35 -0
- package/modules/deploy/templates/.gitlab/ci/deploy-ssh.yml.hbs +83 -0
- package/modules/deploy/templates/.gitlab/ci/release.yml.hbs +26 -0
- package/modules/git/.husky/commit-msg +8 -4
- package/modules/git/.husky/pre-commit +0 -3
- package/modules/git/index.js +34 -38
- package/modules/git/templates/.lando.yml.hbs +13 -5
- package/modules/git/templates/package.json.hbs +4 -4
- package/modules/git/templates/workspace.json.hbs +114 -28
- package/modules/lint/templates/.lando.yml.hbs +10 -2
- package/modules/lint/templates/package.json.hbs +4 -4
- package/modules/lint/templates/workspace.json.hbs +56 -15
- package/modules/php/templates/.lando.yml.hbs +11 -2
- package/modules/php/templates/composer.json.hbs +1 -1
- package/modules/php/templates/workspace.json.hbs +74 -15
- package/modules/redis/templates/.lando.yml.hbs +8 -1
- package/modules/sage/index.js +114 -15
- package/modules/sage/prompts.js +1 -1
- package/modules/sage/templates/.devcontainer/devcontainer.json.hbs +7 -0
- package/modules/sage/templates/.devcontainer/docker-compose.override.yml.hbs +6 -0
- package/modules/sage/templates/.devcontainer/post-create.sh.hbs +11 -0
- package/modules/sage/templates/.lando.yml.hbs +49 -20
- package/modules/sage/templates/server/cmd/setup-sage-node.sh.hbs +23 -0
- package/modules/sage/templates/server/cmd/setup-sage-php.sh.hbs +24 -0
- package/modules/sage/templates/theme/app/Blocks/.gitkeep.hbs +0 -0
- package/modules/sage/templates/theme/app/Fields/.gitkeep.hbs +0 -0
- package/modules/sage/templates/theme/composer.json.hbs +25 -3
- package/modules/sage/templates/theme/package.json.hbs +15 -3
- package/modules/sage/templates/theme/resources/views/blocks/.gitkeep.hbs +0 -0
- package/modules/sage/templates/theme/vite.config.js.hbs +55 -13
- package/modules/sage/templates/workspace.json.hbs +155 -12
- package/modules/skills/index.js +18 -0
- package/modules/skills/prompts.js +21 -0
- package/modules/test/index.js +19 -0
- package/modules/test/prompts.js +8 -0
- package/modules/test/templates/composer.json.hbs +16 -0
- package/modules/test/templates/package.json.hbs +11 -0
- package/modules/test/templates/playwright.config.js.hbs +29 -0
- package/modules/test/tests/Pest.php +39 -0
- package/modules/test/tests/SmokeTest.php +15 -0
- package/modules/test/tests/e2e/example.spec.js +11 -0
- package/modules/test/tests/e2e/helpers/loginHelper.js +40 -0
- package/package.json +1 -1
- package/templates/.devcontainer/Dockerfile.hbs +24 -0
- package/templates/.devcontainer/config/nginx.conf +23 -0
- package/templates/.devcontainer/config/nginx.conf.hbs +23 -0
- package/templates/.devcontainer/devcontainer.json.hbs +29 -0
- package/templates/.devcontainer/docker-compose.yml.hbs +73 -0
- package/templates/.devcontainer/setup.sh.hbs +17 -0
- package/templates/.env.hbs +38 -15
- package/templates/.gitignore.hbs +2 -2
- package/templates/.lando.yml.hbs +88 -11
- package/templates/README.md.hbs +23 -7
- package/templates/composer.json.hbs +4 -3
- package/templates/package.json.hbs +0 -2
- package/templates/server/cmd/install-wp.sh.hbs +54 -43
- package/templates/server/cmd/setup-node.sh.hbs +11 -0
- package/templates/server/cmd/setup-php.sh.hbs +14 -0
- package/templates/server/www/vhosts.conf.hbs +54 -17
- package/templates/workspace.json.hbs +233 -40
- package/templates/wp-cli.yml.hbs +1 -0
package/lib/commands/init.js
CHANGED
|
@@ -9,7 +9,6 @@ import { Generator } from '../core/generator.js';
|
|
|
9
9
|
import { log, color } from '../utils/logger.js';
|
|
10
10
|
import { inferCiCapabilities } from '../core/infer-ci-capabilities.js';
|
|
11
11
|
import { validateVersionCompatibility } from '../config/versions.js';
|
|
12
|
-
import { installSuperpowersSage } from '../utils/git.js';
|
|
13
12
|
|
|
14
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -20,12 +19,11 @@ export const initCommand = new Command('init')
|
|
|
20
19
|
.option('--debug', 'Ativa modo debug para logs detalhados')
|
|
21
20
|
.action(async ({ output, defaults, debug }) => {
|
|
22
21
|
const target = path.resolve(output);
|
|
23
|
-
const { enabledModules, ...context } = await runPrompts({ defaults });
|
|
22
|
+
const { enabledModules: promptedModules, ...context } = await runPrompts({ defaults });
|
|
23
|
+
let enabledModules = [...promptedModules];
|
|
24
24
|
|
|
25
25
|
// Normalização de chaves para consistência entre módulos
|
|
26
|
-
|
|
27
|
-
context.useSage = true;
|
|
28
|
-
}
|
|
26
|
+
context.useSage = context.enable_sage === true;
|
|
29
27
|
if (context.git_provider && !context.gitProvider) {
|
|
30
28
|
context.gitProvider = context.git_provider;
|
|
31
29
|
}
|
|
@@ -34,12 +32,12 @@ export const initCommand = new Command('init')
|
|
|
34
32
|
if (context.useSage && context.sageVersion === '11') {
|
|
35
33
|
context.phpVersion = '8.3';
|
|
36
34
|
context.nodeVersion = '22';
|
|
37
|
-
context.wpCoreVersion = '6.
|
|
35
|
+
context.wpCoreVersion = '6.9.4';
|
|
38
36
|
|
|
39
37
|
log(color.orange, '🔧 Sage 11 selected — automatically adjusting versions:');
|
|
40
38
|
log(color.orange, ' → PHP: 8.3');
|
|
41
39
|
log(color.orange, ' → Node: 22');
|
|
42
|
-
log(color.orange, ' → WP Core: 6.
|
|
40
|
+
log(color.orange, ' → WP Core: 6.9.4');
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
// 📋 Validar compatibilidade de versões (se Sage está habilitado)
|
|
@@ -71,6 +69,9 @@ export const initCommand = new Command('init')
|
|
|
71
69
|
if (context.ci.theme.paths.themeRoot) {
|
|
72
70
|
log(color.blue, ` → themeRoot: ${context.ci.theme.paths.themeRoot}`);
|
|
73
71
|
}
|
|
72
|
+
if (context.useSage) {
|
|
73
|
+
log(color.blue, ' → sageMode: advanced (automatic when Sage is enabled)');
|
|
74
|
+
}
|
|
74
75
|
|
|
75
76
|
const generator = new Generator(target);
|
|
76
77
|
await fs.ensureDir(target);
|
|
@@ -80,6 +81,13 @@ export const initCommand = new Command('init')
|
|
|
80
81
|
await generator.generateFile('.env', context);
|
|
81
82
|
await generator.generateFile('.editorconfig', context);
|
|
82
83
|
await generator.generateFile('server/cmd/install-wp.sh', context);
|
|
84
|
+
await generator.generateFile('server/cmd/setup-php.sh', context);
|
|
85
|
+
await generator.generateFile('server/cmd/setup-node.sh', context);
|
|
86
|
+
await generator.generateFile('.devcontainer/devcontainer.json', context);
|
|
87
|
+
await generator.generateFile('.devcontainer/docker-compose.yml', context);
|
|
88
|
+
await generator.generateFile('.devcontainer/Dockerfile', context);
|
|
89
|
+
await generator.generateFile('.devcontainer/setup.sh', context);
|
|
90
|
+
await generator.generateFile('.devcontainer/config/nginx.conf', context);
|
|
83
91
|
|
|
84
92
|
await generator.copyFile('.cspell.json');
|
|
85
93
|
|
|
@@ -93,6 +101,9 @@ export const initCommand = new Command('init')
|
|
|
93
101
|
|
|
94
102
|
await fs.chmod(path.join(target, 'content/uploads'), 0o777);
|
|
95
103
|
await fs.chmod(path.join(target, 'server/cmd/install-wp.sh'), 0o755);
|
|
104
|
+
await fs.chmod(path.join(target, 'server/cmd/setup-php.sh'), 0o755);
|
|
105
|
+
await fs.chmod(path.join(target, 'server/cmd/setup-node.sh'), 0o755);
|
|
106
|
+
await fs.chmod(path.join(target, '.devcontainer/setup.sh'), 0o755);
|
|
96
107
|
|
|
97
108
|
await generator.generateFile('server/www/vhosts.conf', context);
|
|
98
109
|
if (context.useRocket) {
|
|
@@ -101,6 +112,7 @@ export const initCommand = new Command('init')
|
|
|
101
112
|
|
|
102
113
|
await generator.copyFile('wp-config.php');
|
|
103
114
|
await generator.copyFile('index.php');
|
|
115
|
+
await generator.generateFile('wp-cli.yml', context);
|
|
104
116
|
|
|
105
117
|
log(color.blue, `🧩 Enabled modules: ${enabledModules.join(', ')}`);
|
|
106
118
|
|
|
@@ -154,8 +166,5 @@ export const initCommand = new Command('init')
|
|
|
154
166
|
// Gera CLAUDE.md com overview do projeto e referência ao AI Co-Pilot
|
|
155
167
|
await generator.generateFile('CLAUDE.md', context);
|
|
156
168
|
|
|
157
|
-
// Instala superpowers-sage em .claude/ do projeto gerado
|
|
158
|
-
await installSuperpowersSage(target);
|
|
159
|
-
|
|
160
169
|
log(color.green, '✅ Project initialized with templates.');
|
|
161
170
|
});
|
package/lib/config/versions.js
CHANGED
|
@@ -53,8 +53,8 @@ export const VERSION_REGISTRY = {
|
|
|
53
53
|
* Only actively supported versions per php.net
|
|
54
54
|
*/
|
|
55
55
|
php: {
|
|
56
|
-
latest: '8.
|
|
57
|
-
supported: ['8.3', '8.2', '8.1'],
|
|
56
|
+
latest: '8.4',
|
|
57
|
+
supported: ['8.4', '8.3', '8.2', '8.1'],
|
|
58
58
|
eol: ['8.0', '7.4', '7.3', '7.2'], // Deprecated - do not use
|
|
59
59
|
description: 'Supported PHP versions. PHP 7.4+ reached EOL on 2022-11-28',
|
|
60
60
|
// PHP version dates: https://www.php.net/supported-versions.php
|
|
@@ -70,9 +70,9 @@ export const VERSION_REGISTRY = {
|
|
|
70
70
|
* Tracks LTS and current stable releases
|
|
71
71
|
*/
|
|
72
72
|
node: {
|
|
73
|
-
latest: '
|
|
74
|
-
lts: ['22', '20', '18'],
|
|
75
|
-
all: ['22', '20', '18'],
|
|
73
|
+
latest: '24',
|
|
74
|
+
lts: ['24', '22', '20', '18'],
|
|
75
|
+
all: ['24', '22', '20', '18'],
|
|
76
76
|
deprecated: ['16', '14', '12', '10'],
|
|
77
77
|
description: 'Supported Node.js versions - prefer LTS releases',
|
|
78
78
|
// LTS information: https://nodejs.org/en/about/releases/
|
package/lib/prompts/index.js
CHANGED
|
@@ -6,6 +6,14 @@ import { Generator } from '../core/generator.js';
|
|
|
6
6
|
|
|
7
7
|
import { loadModulePrompts } from './loadModulePrompts.js';
|
|
8
8
|
|
|
9
|
+
function parseBooleanEnv(value) {
|
|
10
|
+
if (typeof value !== 'string') return null;
|
|
11
|
+
const normalized = value.trim().toLowerCase();
|
|
12
|
+
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
13
|
+
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
export async function runPrompts({ defaults = false } = {}) {
|
|
10
18
|
const defaultsGit = getGitUserDefaults();
|
|
11
19
|
|
|
@@ -46,20 +54,7 @@ export async function runPrompts({ defaults = false } = {}) {
|
|
|
46
54
|
message: 'Repository URL:',
|
|
47
55
|
default: defaultsGit.gitRepository,
|
|
48
56
|
},
|
|
49
|
-
|
|
50
|
-
type: 'list',
|
|
51
|
-
name: 'phpVersion',
|
|
52
|
-
message: 'PHP version:',
|
|
53
|
-
choices: ['8.3', '8.2', '8.1'],
|
|
54
|
-
default: '8.3',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
type: 'list',
|
|
58
|
-
name: 'nodeVersion',
|
|
59
|
-
message: 'Node.js version:',
|
|
60
|
-
choices: ['22', '20', '18'],
|
|
61
|
-
default: '22',
|
|
62
|
-
},
|
|
57
|
+
// phpVersion and nodeVersion are locked to '8.4' / '24' — injected into context, not prompted
|
|
63
58
|
{
|
|
64
59
|
type: 'list',
|
|
65
60
|
name: 'wpCoreVersion',
|
|
@@ -92,11 +87,24 @@ export async function runPrompts({ defaults = false } = {}) {
|
|
|
92
87
|
const def = prompt.default;
|
|
93
88
|
defaultAnswers[prompt.name] = typeof def === 'function' ? def() : def;
|
|
94
89
|
}
|
|
90
|
+
|
|
91
|
+
const envEnableSage = parseBooleanEnv(process.env.WP_KIT_ENABLE_SAGE);
|
|
92
|
+
if (envEnableSage !== null) {
|
|
93
|
+
defaultAnswers.enable_sage = envEnableSage;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const envGitProvider = process.env.WP_KIT_GIT_PROVIDER;
|
|
97
|
+
if (envGitProvider) {
|
|
98
|
+
defaultAnswers.gitProvider = envGitProvider;
|
|
99
|
+
}
|
|
100
|
+
|
|
95
101
|
const enabledModules = Object.keys(modulePromptDefs).filter(
|
|
96
102
|
(mod) => defaultAnswers[`enable_${mod}`] === true
|
|
97
103
|
);
|
|
98
104
|
return {
|
|
99
105
|
...defaultAnswers,
|
|
106
|
+
phpVersion: '8.4',
|
|
107
|
+
nodeVersion: '24',
|
|
100
108
|
enabledModules,
|
|
101
109
|
projectDomain: `${defaultAnswers.projectName}.lndo.site`,
|
|
102
110
|
salts: Generator.generateSalts(),
|
|
@@ -110,6 +118,8 @@ export async function runPrompts({ defaults = false } = {}) {
|
|
|
110
118
|
);
|
|
111
119
|
return {
|
|
112
120
|
...answers,
|
|
121
|
+
phpVersion: '8.4',
|
|
122
|
+
nodeVersion: '24',
|
|
113
123
|
enabledModules,
|
|
114
124
|
projectDomain: `${answers.projectName}.lndo.site`,
|
|
115
125
|
salts: Generator.generateSalts(),
|
package/lib/utils/git.js
CHANGED
|
@@ -23,16 +23,45 @@ export function getGitUserDefaults() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Dirs que cada destino copia do repositório superpowers-sage.
|
|
27
|
+
* A subpasta de destino dentro do projeto é TARGET_CONFIG[key].dest.
|
|
28
|
+
*/
|
|
29
|
+
const TARGET_CONFIG = {
|
|
30
|
+
claude: {
|
|
31
|
+
dest: '.claude/plugins/superpowers-sage',
|
|
32
|
+
dirs: ['.claude-plugin', 'skills', 'agents', 'hooks'],
|
|
33
|
+
label: 'Claude Code (.claude/)',
|
|
34
|
+
},
|
|
35
|
+
cursor: {
|
|
36
|
+
dest: '.cursor/plugins/local/superpowers-sage',
|
|
37
|
+
dirs: ['.cursor-plugin', 'skills', 'agents', 'hooks', 'rules'],
|
|
38
|
+
label: 'Cursor (.cursor/)',
|
|
39
|
+
},
|
|
40
|
+
ai: {
|
|
41
|
+
dest: '.ai/superpowers-sage',
|
|
42
|
+
dirs: ['skills', 'agents', 'hooks'],
|
|
43
|
+
label: 'Agnóstico (.ai/)',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clona codigodoleo/superpowers-sage e copia o conteúdo para os destinos selecionados.
|
|
27
49
|
* Requer git instalado no host.
|
|
50
|
+
*
|
|
28
51
|
* @param {string} targetDir - Caminho absoluto do projeto gerado
|
|
52
|
+
* @param {string[]} targets - Lista de destinos: 'claude' | 'cursor' | 'ai'
|
|
29
53
|
*/
|
|
30
|
-
export async function installSuperpowersSage(targetDir) {
|
|
54
|
+
export async function installSuperpowersSage(targetDir, targets = ['claude']) {
|
|
31
55
|
const repo = 'https://github.com/codigodoleo/superpowers-sage.git';
|
|
32
56
|
const tmpDir = path.join(os.tmpdir(), `superpowers-sage-${Date.now()}`);
|
|
33
|
-
const claudeDir = path.join(targetDir, '.claude');
|
|
34
57
|
|
|
35
|
-
|
|
58
|
+
const validTargets = targets.filter((t) => TARGET_CONFIG[t]);
|
|
59
|
+
if (validTargets.length === 0) {
|
|
60
|
+
log(color.orange, '[skills] Nenhum destino válido informado. Pulando.');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log(color.blue, `🔌 Clonando superpowers-sage para: ${validTargets.join(', ')}...`);
|
|
36
65
|
|
|
37
66
|
const result = spawnSync('git', ['clone', '--depth=1', repo, tmpDir], {
|
|
38
67
|
stdio: 'inherit',
|
|
@@ -41,19 +70,27 @@ export async function installSuperpowersSage(targetDir) {
|
|
|
41
70
|
if (result.status !== 0) {
|
|
42
71
|
log(
|
|
43
72
|
color.orange,
|
|
44
|
-
'⚠️
|
|
73
|
+
'⚠️ Não foi possível clonar superpowers-sage (sem rede ou git indisponível). Pulando.'
|
|
45
74
|
);
|
|
46
75
|
return;
|
|
47
76
|
}
|
|
48
77
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
overwrite: true,
|
|
53
|
-
filter: (src) => !src.includes(`${path.sep}.git`),
|
|
54
|
-
});
|
|
78
|
+
for (const key of validTargets) {
|
|
79
|
+
const cfg = TARGET_CONFIG[key];
|
|
80
|
+
const destBase = path.join(targetDir, cfg.dest);
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
await fs.ensureDir(destBase);
|
|
83
|
+
|
|
84
|
+
for (const dir of cfg.dirs) {
|
|
85
|
+
const srcDir = path.join(tmpDir, dir);
|
|
86
|
+
if (!(await fs.pathExists(srcDir))) continue;
|
|
87
|
+
|
|
88
|
+
const destDir = path.join(destBase, dir);
|
|
89
|
+
await fs.copy(srcDir, destDir, { overwrite: true });
|
|
90
|
+
}
|
|
57
91
|
|
|
58
|
-
|
|
92
|
+
log(color.green, `✅ superpowers-sage instalado em ${cfg.dest}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await fs.remove(tmpDir);
|
|
59
96
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Reusable workflow — Docker Build & Push to Container Registry
|
|
2
|
+
#
|
|
3
|
+
# Como usar no seu workflow principal (.github/workflows/main.yml):
|
|
4
|
+
#
|
|
5
|
+
# on:
|
|
6
|
+
# push:
|
|
7
|
+
# branches: [main]
|
|
8
|
+
#
|
|
9
|
+
# jobs:
|
|
10
|
+
# deploy:
|
|
11
|
+
# uses: ./.github/workflows/deploy-docker.yml
|
|
12
|
+
# with:
|
|
13
|
+
# registry: ghcr.io # opcional — default: ghcr.io
|
|
14
|
+
# # registry: docker.io # para Docker Hub
|
|
15
|
+
# secrets:
|
|
16
|
+
# REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GHCR usa GITHUB_TOKEN
|
|
17
|
+
# # REGISTRY_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} # Docker Hub
|
|
18
|
+
#
|
|
19
|
+
# Secrets necessários (Settings → Secrets → Actions):
|
|
20
|
+
# REGISTRY_TOKEN — token de autenticação no registry (GITHUB_TOKEN é suficiente para GHCR)
|
|
21
|
+
|
|
22
|
+
name: Deploy (Docker)
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
workflow_call:
|
|
26
|
+
inputs:
|
|
27
|
+
registry:
|
|
28
|
+
description: 'Container registry (ghcr.io ou docker.io)'
|
|
29
|
+
required: false
|
|
30
|
+
type: string
|
|
31
|
+
default: ghcr.io
|
|
32
|
+
image-name:
|
|
33
|
+
description: 'Nome da imagem (default: github.repository em lowercase)'
|
|
34
|
+
required: false
|
|
35
|
+
type: string
|
|
36
|
+
default: ''
|
|
37
|
+
dockerfile:
|
|
38
|
+
description: 'Caminho para o Dockerfile'
|
|
39
|
+
required: false
|
|
40
|
+
type: string
|
|
41
|
+
default: Dockerfile
|
|
42
|
+
context:
|
|
43
|
+
description: 'Contexto do build Docker'
|
|
44
|
+
required: false
|
|
45
|
+
type: string
|
|
46
|
+
default: '.'
|
|
47
|
+
secrets:
|
|
48
|
+
REGISTRY_TOKEN:
|
|
49
|
+
required: false
|
|
50
|
+
|
|
51
|
+
permissions:
|
|
52
|
+
contents: read
|
|
53
|
+
packages: write
|
|
54
|
+
|
|
55
|
+
jobs:
|
|
56
|
+
build-push:
|
|
57
|
+
name: Build & Push
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- name: Checkout
|
|
61
|
+
uses: actions/checkout@v4
|
|
62
|
+
|
|
63
|
+
- name: Resolve image name
|
|
64
|
+
id: image
|
|
65
|
+
run: |
|
|
66
|
+
NAME="${{ inputs.image-name }}"
|
|
67
|
+
[ -z "$NAME" ] && NAME="${{ github.repository }}"
|
|
68
|
+
echo "name=${NAME,,}" >> $GITHUB_OUTPUT
|
|
69
|
+
|
|
70
|
+
- name: Set up QEMU
|
|
71
|
+
uses: docker/setup-qemu-action@v3
|
|
72
|
+
|
|
73
|
+
- name: Set up Docker Buildx
|
|
74
|
+
uses: docker/setup-buildx-action@v3
|
|
75
|
+
|
|
76
|
+
- name: Log in to registry
|
|
77
|
+
uses: docker/login-action@v3
|
|
78
|
+
with:
|
|
79
|
+
registry: ${{ inputs.registry }}
|
|
80
|
+
username: ${{ github.actor }}
|
|
81
|
+
password: ${{ secrets.REGISTRY_TOKEN || secrets.GITHUB_TOKEN }}
|
|
82
|
+
|
|
83
|
+
- name: Extract Docker metadata
|
|
84
|
+
id: meta
|
|
85
|
+
uses: docker/metadata-action@v5
|
|
86
|
+
with:
|
|
87
|
+
images: ${{ inputs.registry }}/${{ steps.image.outputs.name }}
|
|
88
|
+
tags: |
|
|
89
|
+
type=ref,event=branch
|
|
90
|
+
type=semver,pattern={{version}}
|
|
91
|
+
type=sha,prefix=sha-,format=short
|
|
92
|
+
|
|
93
|
+
- name: Build and push
|
|
94
|
+
uses: docker/build-push-action@v6
|
|
95
|
+
with:
|
|
96
|
+
context: ${{ inputs.context }}
|
|
97
|
+
file: ${{ inputs.dockerfile }}
|
|
98
|
+
push: true
|
|
99
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
100
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
101
|
+
cache-from: type=gha
|
|
102
|
+
cache-to: type=gha,mode=max
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Reusable workflow — SSH Deploy via rsync
|
|
2
|
+
#
|
|
3
|
+
# Como usar no seu workflow principal (.github/workflows/main.yml):
|
|
4
|
+
#
|
|
5
|
+
# on:
|
|
6
|
+
# push:
|
|
7
|
+
# branches: [main]
|
|
8
|
+
#
|
|
9
|
+
# jobs:
|
|
10
|
+
# deploy:
|
|
11
|
+
# uses: ./.github/workflows/deploy-ssh.yml
|
|
12
|
+
# with:
|
|
13
|
+
# host: meu-servidor.example.com
|
|
14
|
+
# user: deploy
|
|
15
|
+
# remote-path: /var/www/html
|
|
16
|
+
# theme-path: content/themes/meu-tema # opcional, para projetos com Sage
|
|
17
|
+
# secrets:
|
|
18
|
+
# SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
19
|
+
# SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
|
|
20
|
+
#
|
|
21
|
+
# Secrets necessários (Settings → Secrets → Actions):
|
|
22
|
+
# SSH_PRIVATE_KEY — chave SSH privada (gerada sem passphrase para CI)
|
|
23
|
+
# SSH_KNOWN_HOSTS — saída de: ssh-keyscan -H <host>
|
|
24
|
+
#
|
|
25
|
+
# Permissões no servidor — o rsync usa --no-perms --no-owner --no-group,
|
|
26
|
+
# portanto não altera permissões/proprietários existentes no destino.
|
|
27
|
+
|
|
28
|
+
name: Deploy (SSH + rsync)
|
|
29
|
+
|
|
30
|
+
on:
|
|
31
|
+
workflow_call:
|
|
32
|
+
inputs:
|
|
33
|
+
host:
|
|
34
|
+
description: 'Endereço do servidor de destino'
|
|
35
|
+
required: true
|
|
36
|
+
type: string
|
|
37
|
+
user:
|
|
38
|
+
description: 'Usuário SSH no servidor'
|
|
39
|
+
required: true
|
|
40
|
+
type: string
|
|
41
|
+
remote-path:
|
|
42
|
+
description: 'Caminho absoluto no servidor de destino'
|
|
43
|
+
required: true
|
|
44
|
+
type: string
|
|
45
|
+
php-version:
|
|
46
|
+
description: 'Versão do PHP para instalar dependências Composer'
|
|
47
|
+
required: false
|
|
48
|
+
type: string
|
|
49
|
+
default: '8.3'
|
|
50
|
+
node-version:
|
|
51
|
+
description: 'Versão do Node.js para build de assets'
|
|
52
|
+
required: false
|
|
53
|
+
type: string
|
|
54
|
+
default: '22'
|
|
55
|
+
theme-path:
|
|
56
|
+
description: 'Caminho relativo do tema para build (vazio = sem build de tema)'
|
|
57
|
+
required: false
|
|
58
|
+
type: string
|
|
59
|
+
default: ''
|
|
60
|
+
rsync-excludes:
|
|
61
|
+
description: 'Caminhos a excluir do rsync, separados por vírgula'
|
|
62
|
+
required: false
|
|
63
|
+
type: string
|
|
64
|
+
default: '.git,.env,node_modules/.cache'
|
|
65
|
+
secrets:
|
|
66
|
+
SSH_PRIVATE_KEY:
|
|
67
|
+
required: true
|
|
68
|
+
SSH_KNOWN_HOSTS:
|
|
69
|
+
required: true
|
|
70
|
+
|
|
71
|
+
jobs:
|
|
72
|
+
build-and-deploy:
|
|
73
|
+
name: Build & Deploy via SSH
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
steps:
|
|
76
|
+
- name: Checkout
|
|
77
|
+
uses: actions/checkout@v4
|
|
78
|
+
with:
|
|
79
|
+
fetch-depth: 0
|
|
80
|
+
|
|
81
|
+
- name: Setup PHP
|
|
82
|
+
uses: shivammathur/setup-php@v2
|
|
83
|
+
with:
|
|
84
|
+
php-version: ${{ inputs.php-version }}
|
|
85
|
+
|
|
86
|
+
- name: Install Composer dependencies
|
|
87
|
+
run: composer install --no-dev --optimize-autoloader --no-interaction
|
|
88
|
+
|
|
89
|
+
- name: Setup Node.js
|
|
90
|
+
if: inputs.theme-path != ''
|
|
91
|
+
uses: actions/setup-node@v4
|
|
92
|
+
with:
|
|
93
|
+
node-version: ${{ inputs.node-version }}
|
|
94
|
+
cache: 'npm'
|
|
95
|
+
cache-dependency-path: ${{ inputs.theme-path }}/package-lock.json
|
|
96
|
+
|
|
97
|
+
- name: Build theme assets
|
|
98
|
+
if: inputs.theme-path != ''
|
|
99
|
+
run: |
|
|
100
|
+
npm install --prefix "${{ inputs.theme-path }}"
|
|
101
|
+
npm run build --prefix "${{ inputs.theme-path }}"
|
|
102
|
+
|
|
103
|
+
- name: Configure SSH agent
|
|
104
|
+
uses: webfactory/ssh-agent@v0.9.0
|
|
105
|
+
with:
|
|
106
|
+
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
107
|
+
|
|
108
|
+
- name: Add known hosts
|
|
109
|
+
run: |
|
|
110
|
+
mkdir -p ~/.ssh
|
|
111
|
+
echo "${{ secrets.SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
|
|
112
|
+
chmod 600 ~/.ssh/known_hosts
|
|
113
|
+
|
|
114
|
+
- name: Deploy via rsync
|
|
115
|
+
run: |
|
|
116
|
+
EXCLUDE_ARGS=""
|
|
117
|
+
IFS=',' read -ra ITEMS <<< "${{ inputs.rsync-excludes }}"
|
|
118
|
+
for item in "${ITEMS[@]}"; do
|
|
119
|
+
EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude=${item// /}"
|
|
120
|
+
done
|
|
121
|
+
|
|
122
|
+
rsync -avz --delete \
|
|
123
|
+
$EXCLUDE_ARGS \
|
|
124
|
+
--no-perms --no-owner --no-group \
|
|
125
|
+
./ \
|
|
126
|
+
"${{ inputs.user }}@${{ inputs.host }}:${{ inputs.remote-path }}/"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Reusable workflow — Semantic Release
|
|
2
|
+
#
|
|
3
|
+
# Como usar no seu workflow principal (.github/workflows/main.yml):
|
|
4
|
+
#
|
|
5
|
+
# on:
|
|
6
|
+
# push:
|
|
7
|
+
# branches: [main]
|
|
8
|
+
#
|
|
9
|
+
# jobs:
|
|
10
|
+
# release:
|
|
11
|
+
# uses: ./.github/workflows/release.yml
|
|
12
|
+
# secrets:
|
|
13
|
+
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # opcional para projetos WordPress
|
|
14
|
+
#
|
|
15
|
+
# GITHUB_TOKEN é injetado automaticamente com os devidos escopos.
|
|
16
|
+
|
|
17
|
+
name: Semantic Release (reusable)
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
workflow_call:
|
|
21
|
+
secrets:
|
|
22
|
+
NPM_TOKEN:
|
|
23
|
+
required: false
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
issues: write
|
|
28
|
+
pull-requests: write
|
|
29
|
+
id-token: write
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
release:
|
|
33
|
+
name: Semantic Release
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout
|
|
37
|
+
uses: actions/checkout@v4
|
|
38
|
+
with:
|
|
39
|
+
fetch-depth: 0
|
|
40
|
+
persist-credentials: false
|
|
41
|
+
|
|
42
|
+
- name: Setup Node.js
|
|
43
|
+
uses: actions/setup-node@v4
|
|
44
|
+
with:
|
|
45
|
+
node-version: '24'
|
|
46
|
+
cache: 'npm'
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: npm ci
|
|
50
|
+
|
|
51
|
+
- name: Release
|
|
52
|
+
run: npx semantic-release
|
|
53
|
+
env:
|
|
54
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
55
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/modules/deploy/index.js
CHANGED
|
@@ -12,6 +12,27 @@ export async function setupModule(context, generator) {
|
|
|
12
12
|
if (context.gitProvider === 'gitlab') {
|
|
13
13
|
await generator.generateFile('.gitlab/gitlab-ci.yml', context, 'deploy', '.gitlab-ci.yml');
|
|
14
14
|
log(color.green, '[deploy] .gitlab-ci.yml gerado com sucesso.');
|
|
15
|
+
|
|
16
|
+
// Jobs reutilizáveis para .gitlab/ci/ (include: no pipeline principal)
|
|
17
|
+
await generator.generateFile(
|
|
18
|
+
'.gitlab/ci/release.yml',
|
|
19
|
+
context,
|
|
20
|
+
'deploy',
|
|
21
|
+
'.gitlab/ci/release.yml'
|
|
22
|
+
);
|
|
23
|
+
await generator.generateFile(
|
|
24
|
+
'.gitlab/ci/deploy-docker.yml',
|
|
25
|
+
context,
|
|
26
|
+
'deploy',
|
|
27
|
+
'.gitlab/ci/deploy-docker.yml'
|
|
28
|
+
);
|
|
29
|
+
await generator.generateFile(
|
|
30
|
+
'.gitlab/ci/deploy-ssh.yml',
|
|
31
|
+
context,
|
|
32
|
+
'deploy',
|
|
33
|
+
'.gitlab/ci/deploy-ssh.yml'
|
|
34
|
+
);
|
|
35
|
+
log(color.green, '[deploy] Jobs reutilizáveis GitLab gerados em .gitlab/ci/');
|
|
15
36
|
}
|
|
16
37
|
if (context.gitProvider === 'github') {
|
|
17
38
|
await generator.generateFile(
|
|
@@ -21,6 +42,24 @@ export async function setupModule(context, generator) {
|
|
|
21
42
|
'.github/workflows/ci.yml'
|
|
22
43
|
);
|
|
23
44
|
log(color.green, '[deploy] GitHub Actions workflow gerado com sucesso.');
|
|
45
|
+
|
|
46
|
+
// Reusable workflows para .github/workflows/ (workflow_call no pipeline principal)
|
|
47
|
+
await generator.copyFile(
|
|
48
|
+
'.github/workflows/release.yml',
|
|
49
|
+
'deploy',
|
|
50
|
+
'.github/workflows/release.yml'
|
|
51
|
+
);
|
|
52
|
+
await generator.copyFile(
|
|
53
|
+
'.github/workflows/deploy-docker.yml',
|
|
54
|
+
'deploy',
|
|
55
|
+
'.github/workflows/deploy-docker.yml'
|
|
56
|
+
);
|
|
57
|
+
await generator.copyFile(
|
|
58
|
+
'.github/workflows/deploy-ssh.yml',
|
|
59
|
+
'deploy',
|
|
60
|
+
'.github/workflows/deploy-ssh.yml'
|
|
61
|
+
);
|
|
62
|
+
log(color.green, '[deploy] Reusable workflows GitHub gerados em .github/workflows/');
|
|
24
63
|
}
|
|
25
64
|
if (context.gitProvider === 'bitbucket') {
|
|
26
65
|
await generator.generateFile(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Job reutilizável — Docker Build & Push to Container Registry
|
|
2
|
+
#
|
|
3
|
+
# Como usar no .gitlab-ci.yml principal:
|
|
4
|
+
#
|
|
5
|
+
# include:
|
|
6
|
+
# - local: '.gitlab/ci/deploy-docker.yml'
|
|
7
|
+
#
|
|
8
|
+
# stages:
|
|
9
|
+
# - deploy
|
|
10
|
+
#
|
|
11
|
+
# Variáveis necessárias (Settings → CI/CD → Variables):
|
|
12
|
+
# CI_REGISTRY_USER — automático para o GitLab Container Registry
|
|
13
|
+
# CI_REGISTRY_PASSWORD — automático para o GitLab Container Registry
|
|
14
|
+
# CI_REGISTRY_IMAGE — automático: registry.gitlab.com/<grupo>/<projeto>
|
|
15
|
+
#
|
|
16
|
+
# Para registry externo (Docker Hub, GHCR), sobrescreva as variáveis acima.
|
|
17
|
+
|
|
18
|
+
deploy:docker:
|
|
19
|
+
stage: deploy
|
|
20
|
+
image: docker:latest
|
|
21
|
+
services:
|
|
22
|
+
- docker:dind
|
|
23
|
+
variables:
|
|
24
|
+
DOCKER_TLS_CERTDIR: '/certs'
|
|
25
|
+
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
|
|
26
|
+
IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
|
|
27
|
+
before_script:
|
|
28
|
+
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
|
|
29
|
+
script:
|
|
30
|
+
- docker build --tag "$IMAGE_TAG" --tag "$IMAGE_LATEST" .
|
|
31
|
+
- docker push "$IMAGE_TAG"
|
|
32
|
+
- docker push "$IMAGE_LATEST"
|
|
33
|
+
rules:
|
|
34
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
35
|
+
- if: $CI_COMMIT_TAG
|