codecrypto-cli 1.0.7 → 1.0.9
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/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +64 -7
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/deploy-sc.d.ts.map +1 -1
- package/dist/commands/deploy-sc.js +162 -0
- package/dist/commands/deploy-sc.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +556 -18
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +518 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/docker-build/Dockerfile +18 -0
- package/docker-build/standalone/.next/BUILD_ID +1 -0
- package/docker-build/standalone/.next/app-path-routes-manifest.json +6 -0
- package/docker-build/standalone/.next/build-manifest.json +19 -0
- package/docker-build/standalone/.next/package.json +1 -0
- package/docker-build/standalone/.next/prerender-manifest.json +114 -0
- package/docker-build/standalone/.next/required-server-files.json +164 -0
- package/docker-build/standalone/.next/routes-manifest.json +68 -0
- package/docker-build/standalone/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
- package/docker-build/standalone/.next/server/app/_global-error/page/build-manifest.json +16 -0
- package/docker-build/standalone/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
- package/docker-build/standalone/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
- package/docker-build/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
- package/docker-build/standalone/.next/server/app/_global-error/page.js +11 -0
- package/docker-build/standalone/.next/server/app/_global-error/page.js.map +5 -0
- package/docker-build/standalone/.next/server/app/_global-error/page.js.nft.json +1 -0
- package/docker-build/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +2 -0
- package/docker-build/standalone/.next/server/app/_global-error.html +2 -0
- package/docker-build/standalone/.next/server/app/_global-error.meta +15 -0
- package/docker-build/standalone/.next/server/app/_global-error.rsc +13 -0
- package/docker-build/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
- package/docker-build/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +13 -0
- package/docker-build/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +6 -0
- package/docker-build/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
- package/docker-build/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
- package/docker-build/standalone/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
- package/docker-build/standalone/.next/server/app/_not-found/page/build-manifest.json +16 -0
- package/docker-build/standalone/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
- package/docker-build/standalone/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
- package/docker-build/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
- package/docker-build/standalone/.next/server/app/_not-found/page.js +14 -0
- package/docker-build/standalone/.next/server/app/_not-found/page.js.map +5 -0
- package/docker-build/standalone/.next/server/app/_not-found/page.js.nft.json +1 -0
- package/docker-build/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +2 -0
- package/docker-build/standalone/.next/server/app/_not-found.html +1 -0
- package/docker-build/standalone/.next/server/app/_not-found.meta +16 -0
- package/docker-build/standalone/.next/server/app/_not-found.rsc +14 -0
- package/docker-build/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -0
- package/docker-build/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
- package/docker-build/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -0
- package/docker-build/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
- package/docker-build/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
- package/docker-build/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
- package/docker-build/standalone/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
- package/docker-build/standalone/.next/server/app/favicon.ico/route/build-manifest.json +11 -0
- package/docker-build/standalone/.next/server/app/favicon.ico/route.js +6 -0
- package/docker-build/standalone/.next/server/app/favicon.ico/route.js.map +5 -0
- package/docker-build/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -0
- package/docker-build/standalone/.next/server/app/favicon.ico.body +0 -0
- package/docker-build/standalone/.next/server/app/favicon.ico.meta +1 -0
- package/docker-build/standalone/.next/server/app/index.html +1 -0
- package/docker-build/standalone/.next/server/app/index.meta +14 -0
- package/docker-build/standalone/.next/server/app/index.rsc +16 -0
- package/docker-build/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -0
- package/docker-build/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -0
- package/docker-build/standalone/.next/server/app/index.segments/_head.segment.rsc +6 -0
- package/docker-build/standalone/.next/server/app/index.segments/_index.segment.rsc +5 -0
- package/docker-build/standalone/.next/server/app/index.segments/_tree.segment.rsc +4 -0
- package/docker-build/standalone/.next/server/app/page/app-paths-manifest.json +3 -0
- package/docker-build/standalone/.next/server/app/page/build-manifest.json +16 -0
- package/docker-build/standalone/.next/server/app/page/next-font-manifest.json +11 -0
- package/docker-build/standalone/.next/server/app/page/react-loadable-manifest.json +1 -0
- package/docker-build/standalone/.next/server/app/page/server-reference-manifest.json +4 -0
- package/docker-build/standalone/.next/server/app/page.js +16 -0
- package/docker-build/standalone/.next/server/app/page.js.map +5 -0
- package/docker-build/standalone/.next/server/app/page.js.nft.json +1 -0
- package/docker-build/standalone/.next/server/app/page_client-reference-manifest.js +2 -0
- package/docker-build/standalone/.next/server/app-paths-manifest.json +6 -0
- package/docker-build/standalone/.next/server/chunks/66d90_example-navidad__next-internal_server_app_favicon_ico_route_actions_a30dceae.js +3 -0
- package/docker-build/standalone/.next/server/chunks/[externals]_next_dist_03fe02e0._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/[root-of-the-server]__4b49000d._.js +21 -0
- package/docker-build/standalone/.next/server/chunks/[turbopack]_runtime.js +795 -0
- package/docker-build/standalone/.next/server/chunks/ssr/10072_infra_example-navidad__next-internal_server_app_page_actions_a387aef9.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/2025_cc_CODECRYPTO_infra_example-navidad_377109b0._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/2025_cc_CODECRYPTO_infra_example-navidad_90799f30._.js +4 -0
- package/docker-build/standalone/.next/server/chunks/ssr/2025_cc_CODECRYPTO_infra_example-navidad_app_b9057478._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/66d90_example-navidad__next-internal_server_app__global-error_page_actions_a079475f.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/66d90_example-navidad__next-internal_server_app__not-found_page_actions_ac88cec2.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_51be1544._.js +4 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_8dba0557._.js +6 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_41330064._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_builtin_forbidden_ff3a0457.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_builtin_global-error_e0d57e6e.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_builtin_unauthorized_f1c47e13.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_d332f292._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_esm_build_templates_app-page_cf6afba1.js +4 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__02f3f427._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__2f460610._.js +4 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__4722e53c._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__53b749fa._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__61942f2d._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__7707ad1b._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__864cadbf._.js +10 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__9939e281._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__b4b0aa8a._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__d2b7072b._.js +3 -0
- package/docker-build/standalone/.next/server/chunks/ssr/[turbopack]_runtime.js +795 -0
- package/docker-build/standalone/.next/server/functions-config-manifest.json +4 -0
- package/docker-build/standalone/.next/server/middleware-build-manifest.js +20 -0
- package/docker-build/standalone/.next/server/middleware-manifest.json +6 -0
- package/docker-build/standalone/.next/server/next-font-manifest.js +1 -0
- package/docker-build/standalone/.next/server/next-font-manifest.json +15 -0
- package/docker-build/standalone/.next/server/pages/404.html +1 -0
- package/docker-build/standalone/.next/server/pages/500.html +2 -0
- package/docker-build/standalone/.next/server/pages-manifest.json +4 -0
- package/docker-build/standalone/.next/server/server-reference-manifest.js +1 -0
- package/docker-build/standalone/.next/server/server-reference-manifest.json +5 -0
- package/docker-build/standalone/.next/static/_NbfI2TKfapiyxsQgIG3h/_buildManifest.js +11 -0
- package/docker-build/standalone/.next/static/_NbfI2TKfapiyxsQgIG3h/_clientMiddlewareManifest.json +1 -0
- package/docker-build/standalone/.next/static/_NbfI2TKfapiyxsQgIG3h/_ssgManifest.js +1 -0
- package/docker-build/standalone/.next/static/chunks/57d1af92f5dc15fa.js +1 -0
- package/docker-build/standalone/.next/static/chunks/6ae71d5e8ea4d1eb.js +1 -0
- package/docker-build/standalone/.next/static/chunks/70977d70c9306213.js +1 -0
- package/docker-build/standalone/.next/static/chunks/777dac7104fe2a30.js +5 -0
- package/docker-build/standalone/.next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/docker-build/standalone/.next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- package/docker-build/standalone/.next/static/chunks/d6aec49b013224a2.css +3 -0
- package/docker-build/standalone/.next/static/chunks/turbopack-0ce517fb6224c1f0.js +4 -0
- package/docker-build/standalone/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/docker-build/standalone/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/docker-build/standalone/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/docker-build/standalone/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/docker-build/standalone/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/docker-build/standalone/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/docker-build/standalone/.next/static/media/favicon.0b3bf435.ico +0 -0
- package/docker-build/standalone/package.json +26 -0
- package/docker-build/standalone/public/file.svg +1 -0
- package/docker-build/standalone/public/globe.svg +1 -0
- package/docker-build/standalone/public/next.svg +1 -0
- package/docker-build/standalone/public/vercel.svg +1 -0
- package/docker-build/standalone/public/window.svg +1 -0
- package/docker-build/standalone/server.js +38 -0
- package/package.json +1 -1
- package/src/commands/auth.ts +71 -8
- package/src/commands/deploy-sc.ts +182 -0
- package/src/commands/deploy.ts +592 -21
- package/src/commands/doctor.ts +498 -0
- package/src/index.ts +2 -0
- package/token.json +69 -0
package/src/commands/deploy.ts
CHANGED
|
@@ -4,13 +4,21 @@ import ora from 'ora';
|
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
7
8
|
|
|
8
9
|
export const deployCommand = new Command('deploy')
|
|
9
10
|
.description('Build and deploy Docker image for Next.js standalone application')
|
|
10
11
|
.argument('<project-path>', 'Path to the Next.js project directory')
|
|
11
12
|
.argument('[dest-folder]', 'Destination folder for Docker build context', './docker-build')
|
|
12
13
|
.option('--skip-build', 'Skip npm run build (assume already built)', false)
|
|
13
|
-
.option('--no-push', 'Build image but do not push to registry'
|
|
14
|
+
.option('--no-push', 'Build image but do not push to registry')
|
|
15
|
+
.option('--deploy', 'Deploy to remote server after building', false)
|
|
16
|
+
.option('--service-name <name>', 'Service name for remote deployment')
|
|
17
|
+
.option('--env-file <path>', 'Path to .env file with environment variables')
|
|
18
|
+
.option('--port <port>', 'Application port', '3000')
|
|
19
|
+
.option('--domain-base <domain>', 'Domain base for deployment', 'proyectos.codecrypto.academy')
|
|
20
|
+
.option('--image-version <version>', 'Image version/tag to deploy (overrides package.json version)')
|
|
21
|
+
.option('--skip-git-check', 'Skip Git repository status check', false)
|
|
14
22
|
.action(async (projectPath: string, destFolder: string, options) => {
|
|
15
23
|
console.log(chalk.blue('\n🐳 CodeCrypto Docker Deployment\n'));
|
|
16
24
|
|
|
@@ -29,20 +37,98 @@ export const deployCommand = new Command('deploy')
|
|
|
29
37
|
process.exit(1);
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
//
|
|
40
|
+
// Validar que no hay archivos pendientes de commit (a menos que se omita)
|
|
41
|
+
if (!options.skipGitCheck) {
|
|
42
|
+
const gitCheckSpinner = ora('Checking Git status...').start();
|
|
43
|
+
try {
|
|
44
|
+
// Verificar si es un repositorio git usando git rev-parse
|
|
45
|
+
// Esto es más robusto que solo verificar .git ya que funciona con submodules y worktrees
|
|
46
|
+
let isGitRepo = false;
|
|
47
|
+
try {
|
|
48
|
+
execSync('git rev-parse --git-dir', {
|
|
49
|
+
cwd: resolvedProjectPath,
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
stdio: 'pipe',
|
|
52
|
+
});
|
|
53
|
+
isGitRepo = true;
|
|
54
|
+
} catch {
|
|
55
|
+
// No es un repositorio git o git no está disponible
|
|
56
|
+
isGitRepo = false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isGitRepo) {
|
|
60
|
+
// Verificar si hay cambios sin commitear
|
|
61
|
+
try {
|
|
62
|
+
const gitStatus = execSync('git status --porcelain', {
|
|
63
|
+
cwd: resolvedProjectPath,
|
|
64
|
+
encoding: 'utf-8',
|
|
65
|
+
stdio: 'pipe',
|
|
66
|
+
}).trim();
|
|
67
|
+
|
|
68
|
+
if (gitStatus) {
|
|
69
|
+
gitCheckSpinner.fail('Uncommitted changes detected');
|
|
70
|
+
console.error(chalk.red('\n❌ Error: Hay archivos pendientes de commit en el repositorio'));
|
|
71
|
+
console.error(chalk.yellow('\n📋 Archivos modificados/sin commitear:'));
|
|
72
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
73
|
+
|
|
74
|
+
// Mostrar los archivos pendientes
|
|
75
|
+
const files = gitStatus.split('\n').filter(line => line.trim());
|
|
76
|
+
files.forEach(file => {
|
|
77
|
+
const status = file.substring(0, 2);
|
|
78
|
+
const fileName = file.substring(3);
|
|
79
|
+
let statusColor = chalk.yellow;
|
|
80
|
+
if (status.includes('A')) statusColor = chalk.green;
|
|
81
|
+
else if (status.includes('D')) statusColor = chalk.red;
|
|
82
|
+
else if (status.includes('M')) statusColor = chalk.yellow;
|
|
83
|
+
console.log(chalk.gray(` ${statusColor(status)} ${chalk.white(fileName)}`));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
87
|
+
console.error(chalk.yellow('\n💡 Por favor, haz commit de tus cambios antes de desplegar:'));
|
|
88
|
+
console.error(chalk.cyan(' git add .'));
|
|
89
|
+
console.error(chalk.cyan(' git commit -m "your message"'));
|
|
90
|
+
console.error(chalk.yellow('\n O usa --skip-git-check para omitir esta validación (no recomendado)'));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
} else {
|
|
93
|
+
gitCheckSpinner.succeed('Git repository is clean');
|
|
94
|
+
}
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
// Si git status falla, puede ser que haya algún problema
|
|
97
|
+
gitCheckSpinner.warn('Could not check Git status, continuing...');
|
|
98
|
+
if (error.message) {
|
|
99
|
+
console.log(chalk.yellow(` Warning: ${error.message}`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
gitCheckSpinner.succeed('Not a Git repository (skipping check)');
|
|
104
|
+
}
|
|
105
|
+
} catch (error: any) {
|
|
106
|
+
gitCheckSpinner.warn('Git check failed, continuing...');
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
console.log(chalk.yellow('⚠️ Git status check skipped (--skip-git-check)'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Leer versión del package.json o usar la versión especificada
|
|
33
113
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
34
|
-
const version = packageJson.version || 'latest';
|
|
114
|
+
const version = options.imageVersion || packageJson.version || 'latest';
|
|
35
115
|
const projectName = path.basename(resolvedProjectPath);
|
|
36
116
|
const imageBase = `jviejo/${projectName}`;
|
|
37
117
|
const imageName = `${imageBase}:${version}`;
|
|
38
118
|
const imageLatest = `${imageBase}:latest`;
|
|
39
119
|
|
|
40
|
-
|
|
120
|
+
// Determinar si hacer push
|
|
121
|
+
// En Commander.js, cuando defines .option('--no-push', ...) sin tercer parámetro:
|
|
122
|
+
// - Por defecto (sin flags): options.push = true (hace push)
|
|
123
|
+
// - Con --no-push: options.push = false (no hace push)
|
|
124
|
+
const shouldPush = options.push !== false;
|
|
125
|
+
|
|
126
|
+
console.log(chalk.gray('Deployment Configuration:'));
|
|
41
127
|
console.log(chalk.white(` Project: ${chalk.green(projectName)}`));
|
|
42
128
|
console.log(chalk.white(` Version: ${chalk.green(version)}`));
|
|
43
129
|
console.log(chalk.white(` Image: ${chalk.green(imageName)}`));
|
|
44
130
|
console.log(chalk.white(` Latest: ${chalk.green(imageLatest)}`));
|
|
45
|
-
console.log(chalk.white(` Push to Registry: ${
|
|
131
|
+
console.log(chalk.white(` Push to Registry: ${shouldPush ? chalk.green('Yes') : chalk.yellow('No')}\n`));
|
|
46
132
|
|
|
47
133
|
// Paso 1: Build del proyecto si es necesario
|
|
48
134
|
if (!options.skipBuild) {
|
|
@@ -112,14 +198,63 @@ export const deployCommand = new Command('deploy')
|
|
|
112
198
|
|
|
113
199
|
copySpinner.succeed('Files copied successfully');
|
|
114
200
|
|
|
115
|
-
//
|
|
201
|
+
// Leer y procesar archivo .env si existe
|
|
202
|
+
let nextPublicEnvVars: string[] = [];
|
|
203
|
+
let runtimeEnvVars: string[] = [];
|
|
204
|
+
const envFilePath = options.envFile
|
|
205
|
+
? path.resolve(options.envFile)
|
|
206
|
+
: path.join(resolvedProjectPath, '.env');
|
|
207
|
+
|
|
208
|
+
if (fs.existsSync(envFilePath)) {
|
|
209
|
+
const envContent = fs.readFileSync(envFilePath, 'utf-8');
|
|
210
|
+
const envLines = envContent.split('\n');
|
|
211
|
+
|
|
212
|
+
for (const line of envLines) {
|
|
213
|
+
const trimmed = line.trim();
|
|
214
|
+
// Ignorar comentarios y líneas vacías
|
|
215
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
216
|
+
|
|
217
|
+
const equalIndex = trimmed.indexOf('=');
|
|
218
|
+
if (equalIndex === -1) continue;
|
|
219
|
+
|
|
220
|
+
const key = trimmed.substring(0, equalIndex).trim();
|
|
221
|
+
const value = trimmed.substring(equalIndex + 1).trim();
|
|
222
|
+
|
|
223
|
+
if (key.startsWith('NEXT_PUBLIC_')) {
|
|
224
|
+
nextPublicEnvVars.push(`${key}=${value}`);
|
|
225
|
+
} else {
|
|
226
|
+
runtimeEnvVars.push(`${key}=${value}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Paso 3: Crear Dockerfile con variables NEXT_PUBLIC_*
|
|
232
|
+
const dockerfileSpinner = ora('Creating Dockerfile...').start();
|
|
116
233
|
const dockerfilePath = path.join(resolvedDestFolder, 'Dockerfile');
|
|
234
|
+
|
|
235
|
+
// Construir ARG y ENV para NEXT_PUBLIC_*
|
|
236
|
+
const buildArgs = nextPublicEnvVars.map(env => {
|
|
237
|
+
const [key, ...valueParts] = env.split('=');
|
|
238
|
+
const value = valueParts.join('=');
|
|
239
|
+
return `ARG ${key}=${value}`;
|
|
240
|
+
}).join('\n');
|
|
241
|
+
|
|
242
|
+
const buildEnvs = nextPublicEnvVars.map(env => {
|
|
243
|
+
const [key, ...valueParts] = env.split('=');
|
|
244
|
+
const value = valueParts.join('=');
|
|
245
|
+
return `ENV ${key}=${value}`;
|
|
246
|
+
}).join('\n');
|
|
247
|
+
|
|
117
248
|
const dockerfileContent = `FROM node:20-alpine
|
|
118
249
|
WORKDIR /app
|
|
119
250
|
|
|
120
251
|
ENV NODE_ENV=production
|
|
121
252
|
ENV PORT=3000
|
|
122
253
|
|
|
254
|
+
${buildArgs}
|
|
255
|
+
|
|
256
|
+
${buildEnvs}
|
|
257
|
+
|
|
123
258
|
# Copiamos el contenido de standalone
|
|
124
259
|
COPY standalone/ .
|
|
125
260
|
|
|
@@ -130,28 +265,100 @@ EXPOSE 3000
|
|
|
130
265
|
CMD ["node", "server.js"]
|
|
131
266
|
`;
|
|
132
267
|
fs.writeFileSync(dockerfilePath, dockerfileContent);
|
|
268
|
+
|
|
269
|
+
dockerfileSpinner.succeed('Dockerfile created');
|
|
270
|
+
|
|
271
|
+
// Mostrar contenido del Dockerfile si hay variables NEXT_PUBLIC_*
|
|
272
|
+
if (nextPublicEnvVars.length > 0) {
|
|
273
|
+
console.log(chalk.gray('\n📄 Dockerfile content:'));
|
|
274
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
275
|
+
console.log(chalk.white(dockerfileContent));
|
|
276
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
277
|
+
console.log(chalk.gray(`\n NEXT_PUBLIC_* variables: ${chalk.cyan(nextPublicEnvVars.length)}`));
|
|
278
|
+
nextPublicEnvVars.forEach(env => {
|
|
279
|
+
const [key] = env.split('=');
|
|
280
|
+
console.log(chalk.gray(` - ${chalk.cyan(key)}`));
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
console.log(chalk.gray('\n📄 Dockerfile created (no NEXT_PUBLIC_* variables found)'));
|
|
284
|
+
}
|
|
133
285
|
|
|
134
286
|
// Paso 4: Verificar/crear builder multi-plataforma
|
|
135
287
|
const builderSpinner = ora('Setting up multi-platform builder...').start();
|
|
136
288
|
try {
|
|
137
|
-
|
|
289
|
+
// Listar builders disponibles, ignorando errores de builders con problemas
|
|
290
|
+
let buildersOutput = '';
|
|
291
|
+
try {
|
|
292
|
+
// Redirigir stderr a /dev/null para ignorar errores de builders problemáticos
|
|
293
|
+
buildersOutput = execSync('docker buildx ls 2>/dev/null', {
|
|
294
|
+
encoding: 'utf-8',
|
|
295
|
+
stdio: 'pipe',
|
|
296
|
+
shell: '/bin/sh',
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
// Si hay errores al listar builders (por ejemplo, builder "cc" con problemas SSH),
|
|
300
|
+
// intentar continuar de todas formas
|
|
301
|
+
console.log(chalk.yellow('⚠️ Warning: Some builders have connection issues, continuing...'));
|
|
302
|
+
}
|
|
303
|
+
|
|
138
304
|
const builderName = 'multiarch-builder';
|
|
139
|
-
if (!
|
|
140
|
-
|
|
305
|
+
if (!buildersOutput.includes(builderName)) {
|
|
306
|
+
builderSpinner.text = 'Creating multi-platform builder...';
|
|
307
|
+
execSync(`docker buildx create --name ${builderName} --use --bootstrap`, {
|
|
308
|
+
stdio: 'pipe',
|
|
309
|
+
// No heredar stderr para evitar errores de otros builders
|
|
310
|
+
});
|
|
141
311
|
} else {
|
|
142
|
-
|
|
143
|
-
|
|
312
|
+
builderSpinner.text = 'Using existing multi-platform builder...';
|
|
313
|
+
try {
|
|
314
|
+
execSync(`docker buildx use ${builderName}`, { stdio: 'pipe' });
|
|
315
|
+
} catch (error) {
|
|
316
|
+
// Si falla, intentar crearlo de nuevo
|
|
317
|
+
builderSpinner.text = 'Recreating multi-platform builder...';
|
|
318
|
+
try {
|
|
319
|
+
execSync(`docker buildx rm ${builderName}`, { stdio: 'pipe' });
|
|
320
|
+
} catch {
|
|
321
|
+
// Ignorar si no existe
|
|
322
|
+
}
|
|
323
|
+
execSync(`docker buildx create --name ${builderName} --use --bootstrap`, { stdio: 'pipe' });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
144
326
|
builderSpinner.succeed('Builder ready');
|
|
145
|
-
} catch (error) {
|
|
327
|
+
} catch (error: any) {
|
|
146
328
|
builderSpinner.fail('Failed to setup builder');
|
|
147
329
|
console.error(chalk.red('Error setting up Docker buildx builder'));
|
|
330
|
+
console.error(chalk.yellow('💡 Tip: If you have problematic builders configured, you can remove them with:'));
|
|
331
|
+
console.error(chalk.cyan(' docker buildx rm <builder-name>'));
|
|
332
|
+
console.error(chalk.yellow(' Or inspect them with: docker buildx inspect <builder-name>'));
|
|
148
333
|
process.exit(1);
|
|
149
334
|
}
|
|
150
335
|
|
|
336
|
+
// Verificar autenticación de Docker si se va a hacer push
|
|
337
|
+
if (shouldPush) {
|
|
338
|
+
const authSpinner = ora('Verifying Docker authentication...').start();
|
|
339
|
+
try {
|
|
340
|
+
// Verificar si hay credenciales de Docker configuradas
|
|
341
|
+
execSync('docker info', { stdio: 'pipe' });
|
|
342
|
+
// Intentar verificar login en Docker Hub
|
|
343
|
+
try {
|
|
344
|
+
execSync('docker pull hello-world:latest', { stdio: 'pipe' });
|
|
345
|
+
authSpinner.succeed('Docker authentication verified');
|
|
346
|
+
} catch {
|
|
347
|
+
// Si no puede hacer pull, verificar al menos que docker está funcionando
|
|
348
|
+
authSpinner.warn('Could not verify Docker Hub authentication. Make sure you are logged in with: docker login');
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
authSpinner.fail('Docker authentication failed');
|
|
352
|
+
console.error(chalk.yellow('\n⚠️ Warning: Docker authentication may be required for push'));
|
|
353
|
+
console.error(chalk.yellow(' Run: docker login'));
|
|
354
|
+
console.error(chalk.yellow(' Or use --no-push to skip pushing to registry\n'));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
151
358
|
// Paso 5: Construir y hacer push
|
|
152
359
|
const buildDockerSpinner = ora('Building Docker image for multiple platforms...').start();
|
|
153
360
|
try {
|
|
154
|
-
const tags =
|
|
361
|
+
const tags = shouldPush
|
|
155
362
|
? [`--tag ${imageName}`, `--tag ${imageLatest}`, '--push']
|
|
156
363
|
: [`--tag ${imageName}`, `--tag ${imageLatest}`, '--load'];
|
|
157
364
|
|
|
@@ -161,6 +368,14 @@ CMD ["node", "server.js"]
|
|
|
161
368
|
--progress=plain \
|
|
162
369
|
.`;
|
|
163
370
|
|
|
371
|
+
console.log(chalk.gray('\n🔨 Build command:'));
|
|
372
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
373
|
+
console.log(chalk.cyan(buildCommand));
|
|
374
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
375
|
+
console.log(chalk.gray(`\n Working directory: ${chalk.cyan(resolvedDestFolder)}`));
|
|
376
|
+
console.log(chalk.gray(` Platforms: ${chalk.cyan('linux/amd64, linux/arm64')}`));
|
|
377
|
+
console.log(chalk.gray(` Push to registry: ${shouldPush ? chalk.green('Yes') : chalk.yellow('No')}\n`));
|
|
378
|
+
|
|
164
379
|
execSync(buildCommand, {
|
|
165
380
|
cwd: resolvedDestFolder,
|
|
166
381
|
stdio: 'inherit'
|
|
@@ -168,32 +383,365 @@ CMD ["node", "server.js"]
|
|
|
168
383
|
|
|
169
384
|
buildDockerSpinner.succeed('Docker image built successfully');
|
|
170
385
|
|
|
171
|
-
console.log(chalk.green('\n✅
|
|
172
|
-
console.log(chalk.gray('Image details:'));
|
|
386
|
+
console.log(chalk.green('\n✅ Image build and push completed successfully!'));
|
|
387
|
+
console.log(chalk.gray('📦 Image details:'));
|
|
388
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
173
389
|
console.log(chalk.white(` Platforms: ${chalk.cyan('linux/amd64, linux/arm64')}`));
|
|
174
390
|
console.log(chalk.white(` Version: ${chalk.cyan(version)}`));
|
|
175
391
|
console.log(chalk.white(` Tags:`));
|
|
176
392
|
console.log(chalk.white(` - ${chalk.cyan(imageName)}`));
|
|
177
393
|
console.log(chalk.white(` - ${chalk.cyan(imageLatest)}`));
|
|
178
|
-
if (
|
|
394
|
+
if (shouldPush) {
|
|
179
395
|
console.log(chalk.white(` Registry: ${chalk.cyan('Docker Hub')}`));
|
|
180
396
|
console.log(chalk.white(` URL: ${chalk.cyan(`https://hub.docker.com/r/${imageBase}`)}`));
|
|
397
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
398
|
+
console.log(chalk.green(`\n✅ Image pushed to Docker Hub successfully!`));
|
|
399
|
+
} else {
|
|
400
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
401
|
+
console.log(chalk.yellow(`\n⚠️ Image built locally (not pushed to registry)`));
|
|
181
402
|
}
|
|
182
403
|
} catch (error) {
|
|
183
404
|
buildDockerSpinner.fail('Docker build failed');
|
|
184
405
|
console.error(chalk.red('\n❌ Error building Docker image'));
|
|
185
|
-
if (
|
|
406
|
+
if (shouldPush) {
|
|
186
407
|
console.error(chalk.yellow('Make sure you are authenticated: docker login'));
|
|
187
408
|
}
|
|
188
409
|
process.exit(1);
|
|
189
410
|
}
|
|
190
411
|
|
|
412
|
+
// Paso 6: Desplegar en servidor remoto si se solicita
|
|
413
|
+
if (options.deploy) {
|
|
414
|
+
// Para despliegue, usar la versión especificada o la del package.json
|
|
415
|
+
const deployImageName = options.imageVersion
|
|
416
|
+
? `${imageBase}:${options.imageVersion}`
|
|
417
|
+
: (shouldPush ? imageName : imageLatest);
|
|
418
|
+
|
|
419
|
+
await deployToRemoteServer({
|
|
420
|
+
imageName: deployImageName,
|
|
421
|
+
serviceName: options.serviceName || projectName,
|
|
422
|
+
domainBase: options.domainBase,
|
|
423
|
+
port: options.port,
|
|
424
|
+
envVars: runtimeEnvVars,
|
|
425
|
+
envFilePath: runtimeEnvVars.length > 0 ? envFilePath : undefined,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
191
429
|
} catch (error: any) {
|
|
192
430
|
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
193
431
|
process.exit(1);
|
|
194
432
|
}
|
|
195
433
|
});
|
|
196
434
|
|
|
435
|
+
// Función para leer certificados Docker desde token.json
|
|
436
|
+
function loadDockerCertificatesFromToken(): { caPem: string; certPem: string; keyPem: string; certPath: string } {
|
|
437
|
+
const tokenFilePath = path.join(os.homedir(), '.codecrypto', 'token.json');
|
|
438
|
+
|
|
439
|
+
if (!fs.existsSync(tokenFilePath)) {
|
|
440
|
+
throw new Error(`Token file not found at ${tokenFilePath}. Please run 'codecrypto auth' first.`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let tokenData: any;
|
|
444
|
+
try {
|
|
445
|
+
tokenData = JSON.parse(fs.readFileSync(tokenFilePath, 'utf-8'));
|
|
446
|
+
} catch (error: any) {
|
|
447
|
+
throw new Error(`Failed to read token file: ${error.message}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!tokenData.adminGlobals || !Array.isArray(tokenData.adminGlobals)) {
|
|
451
|
+
throw new Error('adminGlobals not found in token.json. Please run "codecrypto auth --force" to refresh your token.');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Buscar los certificados Docker en el array adminGlobals
|
|
455
|
+
// Las keys que buscamos son:
|
|
456
|
+
// - docker-client/ca-pem
|
|
457
|
+
// - docker-client/cert.pem
|
|
458
|
+
// - docker-client/key.pem
|
|
459
|
+
const certKeys = {
|
|
460
|
+
ca: 'docker-client/ca-pem',
|
|
461
|
+
cert: 'docker-client/cert.pem',
|
|
462
|
+
key: 'docker-client/key.pem'
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const certificates: { ca?: string; cert?: string; key?: string } = {};
|
|
466
|
+
|
|
467
|
+
// Lista de keys encontradas para debugging
|
|
468
|
+
const foundKeys: string[] = [];
|
|
469
|
+
|
|
470
|
+
// Iterar sobre adminGlobals para encontrar los certificados
|
|
471
|
+
for (const global of tokenData.adminGlobals) {
|
|
472
|
+
if (global && global.key) {
|
|
473
|
+
foundKeys.push(global.key);
|
|
474
|
+
// Buscar cada certificado por su key exacta
|
|
475
|
+
if (global.key === certKeys.ca) {
|
|
476
|
+
certificates.ca = global.value;
|
|
477
|
+
} else if (global.key === certKeys.cert) {
|
|
478
|
+
certificates.cert = global.value;
|
|
479
|
+
} else if (global.key === certKeys.key) {
|
|
480
|
+
certificates.key = global.value;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Verificar que todos los certificados estén presentes
|
|
486
|
+
if (!certificates.ca) {
|
|
487
|
+
const availableKeys = foundKeys.filter(k => k.includes('docker-client')).join(', ') || 'none';
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Certificate not found: ${certKeys.ca} in adminGlobals.\n` +
|
|
490
|
+
` Available docker-client keys: ${availableKeys || 'none'}\n` +
|
|
491
|
+
` Total adminGlobals entries: ${tokenData.adminGlobals.length}\n` +
|
|
492
|
+
` Please run "codecrypto auth --force" to refresh your token.`
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
if (!certificates.cert) {
|
|
496
|
+
const availableKeys = foundKeys.filter(k => k.includes('docker-client')).join(', ') || 'none';
|
|
497
|
+
throw new Error(
|
|
498
|
+
`Certificate not found: ${certKeys.cert} in adminGlobals.\n` +
|
|
499
|
+
` Available docker-client keys: ${availableKeys || 'none'}\n` +
|
|
500
|
+
` Total adminGlobals entries: ${tokenData.adminGlobals.length}\n` +
|
|
501
|
+
` Please run "codecrypto auth --force" to refresh your token.`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
if (!certificates.key) {
|
|
505
|
+
const availableKeys = foundKeys.filter(k => k.includes('docker-client')).join(', ') || 'none';
|
|
506
|
+
throw new Error(
|
|
507
|
+
`Certificate not found: ${certKeys.key} in adminGlobals.\n` +
|
|
508
|
+
` Available docker-client keys: ${availableKeys || 'none'}\n` +
|
|
509
|
+
` Total adminGlobals entries: ${tokenData.adminGlobals.length}\n` +
|
|
510
|
+
` Please run "codecrypto auth --force" to refresh your token.`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Crear directorio para certificados si no existe
|
|
515
|
+
const dockerCertPath = path.join(os.homedir(), '.codecrypto', 'docker-client');
|
|
516
|
+
if (!fs.existsSync(dockerCertPath)) {
|
|
517
|
+
fs.mkdirSync(dockerCertPath, { recursive: true });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Escribir certificados a archivos
|
|
521
|
+
const caPemPath = path.join(dockerCertPath, 'ca.pem');
|
|
522
|
+
const certPemPath = path.join(dockerCertPath, 'cert.pem');
|
|
523
|
+
const keyPemPath = path.join(dockerCertPath, 'key.pem');
|
|
524
|
+
|
|
525
|
+
fs.writeFileSync(caPemPath, certificates.ca, 'utf-8');
|
|
526
|
+
fs.writeFileSync(certPemPath, certificates.cert, 'utf-8');
|
|
527
|
+
fs.writeFileSync(keyPemPath, certificates.key, 'utf-8');
|
|
528
|
+
|
|
529
|
+
// Establecer permisos correctos para la clave privada (solo lectura para el propietario)
|
|
530
|
+
try {
|
|
531
|
+
fs.chmodSync(keyPemPath, 0o600);
|
|
532
|
+
} catch (error) {
|
|
533
|
+
// Ignorar errores de chmod en Windows
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
caPem: certificates.ca,
|
|
538
|
+
certPem: certificates.cert,
|
|
539
|
+
keyPem: certificates.key,
|
|
540
|
+
certPath: dockerCertPath
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Función para desplegar en servidor remoto
|
|
545
|
+
async function deployToRemoteServer(config: {
|
|
546
|
+
imageName: string;
|
|
547
|
+
serviceName: string;
|
|
548
|
+
domainBase: string;
|
|
549
|
+
port: string;
|
|
550
|
+
envVars: string[];
|
|
551
|
+
envFilePath?: string;
|
|
552
|
+
}) {
|
|
553
|
+
const deploySpinner = ora('Configuring remote Docker connection...').start();
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
// Cargar certificados desde token.json
|
|
557
|
+
let dockerCertPath: string;
|
|
558
|
+
try {
|
|
559
|
+
const certs = loadDockerCertificatesFromToken();
|
|
560
|
+
dockerCertPath = certs.certPath;
|
|
561
|
+
deploySpinner.succeed('Docker certificates loaded from token.json');
|
|
562
|
+
} catch (error: any) {
|
|
563
|
+
deploySpinner.fail('Failed to load Docker certificates');
|
|
564
|
+
console.error(chalk.red(`❌ Error: ${error.message}`));
|
|
565
|
+
console.error(chalk.yellow('\n💡 To fix this issue:'));
|
|
566
|
+
console.error(chalk.yellow(' 1. Run: codecrypto auth --force'));
|
|
567
|
+
console.error(chalk.yellow(' 2. This will refresh your token with the latest adminGlobals'));
|
|
568
|
+
console.error(chalk.yellow(' 3. Then try the deploy command again\n'));
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Verificar que existen los certificados necesarios
|
|
573
|
+
const requiredCerts = ['ca.pem', 'cert.pem', 'key.pem'];
|
|
574
|
+
for (const cert of requiredCerts) {
|
|
575
|
+
const certPath = path.join(dockerCertPath, cert);
|
|
576
|
+
if (!fs.existsSync(certPath)) {
|
|
577
|
+
deploySpinner.fail(`Certificate ${cert} not found`);
|
|
578
|
+
console.error(chalk.red(`❌ Error: Certificate ${cert} not found at ${certPath}`));
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Construir prefijo de comando Docker con parámetros TLS
|
|
584
|
+
const dockerHost = 'tcp://server.codecrypto.academy:2376';
|
|
585
|
+
const dockerTlsPrefix = `docker -H ${dockerHost} \\
|
|
586
|
+
--tlsverify \\
|
|
587
|
+
--tlscacert="${path.join(dockerCertPath, 'ca.pem')}" \\
|
|
588
|
+
--tlscert="${path.join(dockerCertPath, 'cert.pem')}" \\
|
|
589
|
+
--tlskey="${path.join(dockerCertPath, 'key.pem')}"`;
|
|
590
|
+
|
|
591
|
+
console.log(chalk.gray('\n🔌 Remote Docker configuration:'));
|
|
592
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
593
|
+
console.log(chalk.white(` Docker Host: ${chalk.cyan(dockerHost)}`));
|
|
594
|
+
console.log(chalk.white(` TLS Verify: ${chalk.cyan('enabled')}`));
|
|
595
|
+
console.log(chalk.white(` Certificates Source: ${chalk.cyan('token.json (adminGlobals)')}`));
|
|
596
|
+
console.log(chalk.white(` Certificates Path: ${chalk.cyan(dockerCertPath)}`));
|
|
597
|
+
console.log(chalk.gray(' Certificates loaded:'));
|
|
598
|
+
requiredCerts.forEach(cert => {
|
|
599
|
+
const certPath = path.join(dockerCertPath, cert);
|
|
600
|
+
const exists = fs.existsSync(certPath);
|
|
601
|
+
console.log(chalk.gray(` ${exists ? '✓' : '✗'} ${chalk.cyan(cert)}`));
|
|
602
|
+
});
|
|
603
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
604
|
+
console.log(chalk.yellow('\n💡 All docker commands will use TLS parameters directly\n'));
|
|
605
|
+
|
|
606
|
+
// Construir nombre completo del dominio
|
|
607
|
+
const fullDomain = `${config.serviceName}.${config.domainBase}`;
|
|
608
|
+
const serviceName = config.serviceName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
609
|
+
|
|
610
|
+
// Detener y eliminar contenedor existente si existe
|
|
611
|
+
const stopSpinner = ora('Checking and removing existing container...').start();
|
|
612
|
+
try {
|
|
613
|
+
// Verificar si el contenedor existe
|
|
614
|
+
const checkCmd = `${dockerTlsPrefix} ps -a --filter name=^${fullDomain}$ --format "{{.Names}}"`;
|
|
615
|
+
const existingContainer = execSync(checkCmd, {
|
|
616
|
+
stdio: 'pipe',
|
|
617
|
+
encoding: 'utf-8',
|
|
618
|
+
shell: '/bin/sh',
|
|
619
|
+
}).trim();
|
|
620
|
+
|
|
621
|
+
if (existingContainer === fullDomain) {
|
|
622
|
+
// Contenedor existe, detenerlo primero (si está corriendo)
|
|
623
|
+
try {
|
|
624
|
+
const stopCmd = `${dockerTlsPrefix} stop ${fullDomain}`;
|
|
625
|
+
execSync(stopCmd, { stdio: 'pipe', shell: '/bin/sh' });
|
|
626
|
+
console.log(chalk.gray(` ✓ Container ${fullDomain} stopped`));
|
|
627
|
+
} catch (error: any) {
|
|
628
|
+
// Container might not be running, that's okay
|
|
629
|
+
console.log(chalk.gray(` ℹ Container ${fullDomain} was not running`));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Eliminar el contenedor
|
|
633
|
+
try {
|
|
634
|
+
const rmCmd = `${dockerTlsPrefix} rm -f ${fullDomain}`;
|
|
635
|
+
execSync(rmCmd, { stdio: 'pipe', shell: '/bin/sh' });
|
|
636
|
+
stopSpinner.succeed(`Existing container ${fullDomain} removed`);
|
|
637
|
+
} catch (error: any) {
|
|
638
|
+
stopSpinner.fail(`Failed to remove container ${fullDomain}`);
|
|
639
|
+
console.error(chalk.red(` Error: ${error.message}`));
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
stopSpinner.succeed('No existing container found');
|
|
644
|
+
}
|
|
645
|
+
} catch (error: any) {
|
|
646
|
+
// Si el comando falla, puede ser que el contenedor no exista
|
|
647
|
+
// Intentar eliminarlo de todas formas con -f para forzar
|
|
648
|
+
try {
|
|
649
|
+
const forceRmCmd = `${dockerTlsPrefix} rm -f ${fullDomain}`;
|
|
650
|
+
execSync(forceRmCmd, { stdio: 'pipe', shell: '/bin/sh' });
|
|
651
|
+
stopSpinner.succeed('Existing container removed (forced)');
|
|
652
|
+
} catch {
|
|
653
|
+
// Si falla, asumir que no existe y continuar
|
|
654
|
+
stopSpinner.succeed('No existing container found');
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Preparar opciones de entorno
|
|
659
|
+
// Para despliegue remoto, pasamos las variables directamente con -e
|
|
660
|
+
// ya que --env-file requiere que el archivo esté en el servidor remoto
|
|
661
|
+
const envOpts: string[] = [];
|
|
662
|
+
if (config.envVars.length > 0) {
|
|
663
|
+
config.envVars.forEach(env => {
|
|
664
|
+
// Escapar comillas y caracteres especiales en el valor
|
|
665
|
+
const [key, ...valueParts] = env.split('=');
|
|
666
|
+
const value = valueParts.join('=');
|
|
667
|
+
// Escapar comillas dobles en el valor
|
|
668
|
+
const escapedValue = value.replace(/"/g, '\\"');
|
|
669
|
+
envOpts.push(`-e "${key}=${escapedValue}"`);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Construir comando docker run con parámetros TLS
|
|
674
|
+
const dockerRunCmd = `${dockerTlsPrefix} run -d \\
|
|
675
|
+
--name ${fullDomain} \\
|
|
676
|
+
--network academy-network \\
|
|
677
|
+
--restart unless-stopped \\
|
|
678
|
+
${envOpts.length > 0 ? envOpts.join(' \\\n ') + ' \\' : ''}
|
|
679
|
+
-l traefik.enable=true \\
|
|
680
|
+
-l "traefik.http.routers.${serviceName}-router.rule=Host(\\\`${fullDomain}\\\`)" \\
|
|
681
|
+
-l traefik.http.routers.${serviceName}-router.entrypoints=websecure \\
|
|
682
|
+
-l traefik.http.routers.${serviceName}-router.tls=true \\
|
|
683
|
+
-l traefik.http.services.${serviceName}-service.loadbalancer.server.port=${config.port} \\
|
|
684
|
+
${config.imageName}`;
|
|
685
|
+
|
|
686
|
+
console.log(chalk.gray('\n🚀 Deployment command (executing on remote Docker):'));
|
|
687
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
688
|
+
console.log(chalk.cyan(dockerRunCmd));
|
|
689
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
690
|
+
console.log(chalk.gray(`\n Remote Docker: ${chalk.cyan(dockerHost)}`));
|
|
691
|
+
console.log(chalk.gray(` TLS Certificates: ${chalk.cyan(dockerCertPath)}`));
|
|
692
|
+
console.log(chalk.gray(` Service name: ${chalk.cyan(config.serviceName)}`));
|
|
693
|
+
console.log(chalk.gray(` Full domain: ${chalk.cyan(fullDomain)}`));
|
|
694
|
+
console.log(chalk.gray(` Image: ${chalk.cyan(config.imageName)}`));
|
|
695
|
+
console.log(chalk.gray(` Port: ${chalk.cyan(config.port)}`));
|
|
696
|
+
console.log(chalk.gray(` Environment variables: ${chalk.cyan(config.envVars.length)}`));
|
|
697
|
+
if (config.envVars.length > 0) {
|
|
698
|
+
config.envVars.forEach(env => {
|
|
699
|
+
const [key] = env.split('=');
|
|
700
|
+
console.log(chalk.gray(` - ${chalk.cyan(key)}`));
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
704
|
+
|
|
705
|
+
const runSpinner = ora('Deploying container to remote server...').start();
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
// Ejecutar comando docker con parámetros TLS directamente
|
|
709
|
+
execSync(dockerRunCmd, {
|
|
710
|
+
stdio: 'inherit',
|
|
711
|
+
shell: '/bin/sh',
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
runSpinner.succeed('Container deployed successfully');
|
|
715
|
+
|
|
716
|
+
console.log(chalk.green('\n✅ Remote deployment completed successfully!'));
|
|
717
|
+
console.log(chalk.gray('📋 Deployment details:'));
|
|
718
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
719
|
+
console.log(chalk.white(` Service: ${chalk.cyan(config.serviceName)}`));
|
|
720
|
+
console.log(chalk.white(` Domain: ${chalk.cyan(fullDomain)}`));
|
|
721
|
+
console.log(chalk.white(` URL: ${chalk.cyan(`https://${fullDomain}`)}`));
|
|
722
|
+
console.log(chalk.white(` Image: ${chalk.cyan(config.imageName)}`));
|
|
723
|
+
console.log(chalk.white(` Port: ${chalk.cyan(config.port)}`));
|
|
724
|
+
console.log(chalk.white(` Network: ${chalk.cyan('academy-network')}`));
|
|
725
|
+
console.log(chalk.white(` Restart policy: ${chalk.cyan('unless-stopped')}`));
|
|
726
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
727
|
+
console.log(chalk.green(`\n🌐 Your application is now available at: ${chalk.cyan.underline(`https://${fullDomain}`)}`));
|
|
728
|
+
} catch (error) {
|
|
729
|
+
runSpinner.fail('Failed to deploy container');
|
|
730
|
+
console.error(chalk.red('\n❌ Error deploying container to remote server'));
|
|
731
|
+
console.error(chalk.yellow(' Make sure:'));
|
|
732
|
+
console.error(chalk.yellow(' - The image is available on the remote server'));
|
|
733
|
+
console.error(chalk.yellow(' - You have push the image to Docker Hub (or use --no-push and ensure image exists)'));
|
|
734
|
+
console.error(chalk.yellow(' - The network "academy-network" exists on the remote server'));
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
} catch (error: any) {
|
|
739
|
+
deploySpinner.fail('Failed to configure remote deployment');
|
|
740
|
+
console.error(chalk.red(`\n❌ Error: ${error.message}`));
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
197
745
|
// Helper functions
|
|
198
746
|
function findDirectory(root: string, name: string): string | null {
|
|
199
747
|
try {
|
|
@@ -224,10 +772,33 @@ function copyDirectory(src: string, dest: string): void {
|
|
|
224
772
|
const srcPath = path.join(src, entry.name);
|
|
225
773
|
const destPath = path.join(dest, entry.name);
|
|
226
774
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
775
|
+
try {
|
|
776
|
+
// Obtener información detallada del archivo para detectar tipos especiales
|
|
777
|
+
const stats = fs.lstatSync(srcPath);
|
|
778
|
+
|
|
779
|
+
if (stats.isDirectory()) {
|
|
780
|
+
copyDirectory(srcPath, destPath);
|
|
781
|
+
} else if (stats.isFile()) {
|
|
782
|
+
// Solo copiar archivos regulares
|
|
783
|
+
fs.copyFileSync(srcPath, destPath);
|
|
784
|
+
} else if (stats.isSymbolicLink()) {
|
|
785
|
+
// Copiar symlinks preservando el target
|
|
786
|
+
const linkTarget = fs.readlinkSync(srcPath);
|
|
787
|
+
fs.symlinkSync(linkTarget, destPath);
|
|
788
|
+
} else {
|
|
789
|
+
// Saltar sockets, FIFOs, y otros tipos especiales
|
|
790
|
+
// Estos no son necesarios para el build de Docker
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
} catch (error: any) {
|
|
794
|
+
// Si hay un error al copiar (por ejemplo, socket), simplemente saltarlo
|
|
795
|
+
// Los sockets y otros archivos especiales no son necesarios para el build
|
|
796
|
+
if (error.code === 'ENOTSUP' || error.code === 'EINVAL' || error.code === 'EOPNOTSUPP') {
|
|
797
|
+
// Saltar silenciosamente sockets y otros archivos especiales
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
// Para otros errores, lanzar el error
|
|
801
|
+
throw error;
|
|
231
802
|
}
|
|
232
803
|
}
|
|
233
804
|
}
|