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