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.
Files changed (154) hide show
  1. package/dist/commands/auth.d.ts.map +1 -1
  2. package/dist/commands/auth.js +64 -7
  3. package/dist/commands/auth.js.map +1 -1
  4. package/dist/commands/deploy-sc.d.ts.map +1 -1
  5. package/dist/commands/deploy-sc.js +162 -0
  6. package/dist/commands/deploy-sc.js.map +1 -1
  7. package/dist/commands/deploy.d.ts.map +1 -1
  8. package/dist/commands/deploy.js +556 -18
  9. package/dist/commands/deploy.js.map +1 -1
  10. package/dist/commands/doctor.d.ts +3 -0
  11. package/dist/commands/doctor.d.ts.map +1 -0
  12. package/dist/commands/doctor.js +518 -0
  13. package/dist/commands/doctor.js.map +1 -0
  14. package/dist/index.js +2 -0
  15. package/dist/index.js.map +1 -1
  16. package/docker-build/Dockerfile +18 -0
  17. package/docker-build/standalone/.next/BUILD_ID +1 -0
  18. package/docker-build/standalone/.next/app-path-routes-manifest.json +6 -0
  19. package/docker-build/standalone/.next/build-manifest.json +19 -0
  20. package/docker-build/standalone/.next/package.json +1 -0
  21. package/docker-build/standalone/.next/prerender-manifest.json +114 -0
  22. package/docker-build/standalone/.next/required-server-files.json +164 -0
  23. package/docker-build/standalone/.next/routes-manifest.json +68 -0
  24. package/docker-build/standalone/.next/server/app/_global-error/page/app-paths-manifest.json +3 -0
  25. package/docker-build/standalone/.next/server/app/_global-error/page/build-manifest.json +16 -0
  26. package/docker-build/standalone/.next/server/app/_global-error/page/next-font-manifest.json +6 -0
  27. package/docker-build/standalone/.next/server/app/_global-error/page/react-loadable-manifest.json +1 -0
  28. package/docker-build/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +4 -0
  29. package/docker-build/standalone/.next/server/app/_global-error/page.js +11 -0
  30. package/docker-build/standalone/.next/server/app/_global-error/page.js.map +5 -0
  31. package/docker-build/standalone/.next/server/app/_global-error/page.js.nft.json +1 -0
  32. package/docker-build/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +2 -0
  33. package/docker-build/standalone/.next/server/app/_global-error.html +2 -0
  34. package/docker-build/standalone/.next/server/app/_global-error.meta +15 -0
  35. package/docker-build/standalone/.next/server/app/_global-error.rsc +13 -0
  36. package/docker-build/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +5 -0
  37. package/docker-build/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +13 -0
  38. package/docker-build/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +6 -0
  39. package/docker-build/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
  40. package/docker-build/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  41. package/docker-build/standalone/.next/server/app/_not-found/page/app-paths-manifest.json +3 -0
  42. package/docker-build/standalone/.next/server/app/_not-found/page/build-manifest.json +16 -0
  43. package/docker-build/standalone/.next/server/app/_not-found/page/next-font-manifest.json +11 -0
  44. package/docker-build/standalone/.next/server/app/_not-found/page/react-loadable-manifest.json +1 -0
  45. package/docker-build/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +4 -0
  46. package/docker-build/standalone/.next/server/app/_not-found/page.js +14 -0
  47. package/docker-build/standalone/.next/server/app/_not-found/page.js.map +5 -0
  48. package/docker-build/standalone/.next/server/app/_not-found/page.js.nft.json +1 -0
  49. package/docker-build/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +2 -0
  50. package/docker-build/standalone/.next/server/app/_not-found.html +1 -0
  51. package/docker-build/standalone/.next/server/app/_not-found.meta +16 -0
  52. package/docker-build/standalone/.next/server/app/_not-found.rsc +14 -0
  53. package/docker-build/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -0
  54. package/docker-build/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  55. package/docker-build/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -0
  56. package/docker-build/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  57. package/docker-build/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
  58. package/docker-build/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -0
  59. package/docker-build/standalone/.next/server/app/favicon.ico/route/app-paths-manifest.json +3 -0
  60. package/docker-build/standalone/.next/server/app/favicon.ico/route/build-manifest.json +11 -0
  61. package/docker-build/standalone/.next/server/app/favicon.ico/route.js +6 -0
  62. package/docker-build/standalone/.next/server/app/favicon.ico/route.js.map +5 -0
  63. package/docker-build/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -0
  64. package/docker-build/standalone/.next/server/app/favicon.ico.body +0 -0
  65. package/docker-build/standalone/.next/server/app/favicon.ico.meta +1 -0
  66. package/docker-build/standalone/.next/server/app/index.html +1 -0
  67. package/docker-build/standalone/.next/server/app/index.meta +14 -0
  68. package/docker-build/standalone/.next/server/app/index.rsc +16 -0
  69. package/docker-build/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -0
  70. package/docker-build/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -0
  71. package/docker-build/standalone/.next/server/app/index.segments/_head.segment.rsc +6 -0
  72. package/docker-build/standalone/.next/server/app/index.segments/_index.segment.rsc +5 -0
  73. package/docker-build/standalone/.next/server/app/index.segments/_tree.segment.rsc +4 -0
  74. package/docker-build/standalone/.next/server/app/page/app-paths-manifest.json +3 -0
  75. package/docker-build/standalone/.next/server/app/page/build-manifest.json +16 -0
  76. package/docker-build/standalone/.next/server/app/page/next-font-manifest.json +11 -0
  77. package/docker-build/standalone/.next/server/app/page/react-loadable-manifest.json +1 -0
  78. package/docker-build/standalone/.next/server/app/page/server-reference-manifest.json +4 -0
  79. package/docker-build/standalone/.next/server/app/page.js +16 -0
  80. package/docker-build/standalone/.next/server/app/page.js.map +5 -0
  81. package/docker-build/standalone/.next/server/app/page.js.nft.json +1 -0
  82. package/docker-build/standalone/.next/server/app/page_client-reference-manifest.js +2 -0
  83. package/docker-build/standalone/.next/server/app-paths-manifest.json +6 -0
  84. package/docker-build/standalone/.next/server/chunks/66d90_example-navidad__next-internal_server_app_favicon_ico_route_actions_a30dceae.js +3 -0
  85. package/docker-build/standalone/.next/server/chunks/[externals]_next_dist_03fe02e0._.js +3 -0
  86. package/docker-build/standalone/.next/server/chunks/[root-of-the-server]__4b49000d._.js +21 -0
  87. package/docker-build/standalone/.next/server/chunks/[turbopack]_runtime.js +795 -0
  88. package/docker-build/standalone/.next/server/chunks/ssr/10072_infra_example-navidad__next-internal_server_app_page_actions_a387aef9.js +3 -0
  89. package/docker-build/standalone/.next/server/chunks/ssr/2025_cc_CODECRYPTO_infra_example-navidad_377109b0._.js +3 -0
  90. package/docker-build/standalone/.next/server/chunks/ssr/2025_cc_CODECRYPTO_infra_example-navidad_90799f30._.js +4 -0
  91. package/docker-build/standalone/.next/server/chunks/ssr/2025_cc_CODECRYPTO_infra_example-navidad_app_b9057478._.js +3 -0
  92. package/docker-build/standalone/.next/server/chunks/ssr/66d90_example-navidad__next-internal_server_app__global-error_page_actions_a079475f.js +3 -0
  93. package/docker-build/standalone/.next/server/chunks/ssr/66d90_example-navidad__next-internal_server_app__not-found_page_actions_ac88cec2.js +3 -0
  94. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_51be1544._.js +4 -0
  95. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_8dba0557._.js +6 -0
  96. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_41330064._.js +3 -0
  97. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_builtin_forbidden_ff3a0457.js +3 -0
  98. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_builtin_global-error_e0d57e6e.js +3 -0
  99. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_client_components_builtin_unauthorized_f1c47e13.js +3 -0
  100. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_d332f292._.js +3 -0
  101. package/docker-build/standalone/.next/server/chunks/ssr/77a20_next_dist_esm_build_templates_app-page_cf6afba1.js +4 -0
  102. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__02f3f427._.js +3 -0
  103. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__2f460610._.js +4 -0
  104. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__4722e53c._.js +3 -0
  105. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__53b749fa._.js +3 -0
  106. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__61942f2d._.js +3 -0
  107. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__7707ad1b._.js +3 -0
  108. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__864cadbf._.js +10 -0
  109. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__9939e281._.js +3 -0
  110. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__b4b0aa8a._.js +3 -0
  111. package/docker-build/standalone/.next/server/chunks/ssr/[root-of-the-server]__d2b7072b._.js +3 -0
  112. package/docker-build/standalone/.next/server/chunks/ssr/[turbopack]_runtime.js +795 -0
  113. package/docker-build/standalone/.next/server/functions-config-manifest.json +4 -0
  114. package/docker-build/standalone/.next/server/middleware-build-manifest.js +20 -0
  115. package/docker-build/standalone/.next/server/middleware-manifest.json +6 -0
  116. package/docker-build/standalone/.next/server/next-font-manifest.js +1 -0
  117. package/docker-build/standalone/.next/server/next-font-manifest.json +15 -0
  118. package/docker-build/standalone/.next/server/pages/404.html +1 -0
  119. package/docker-build/standalone/.next/server/pages/500.html +2 -0
  120. package/docker-build/standalone/.next/server/pages-manifest.json +4 -0
  121. package/docker-build/standalone/.next/server/server-reference-manifest.js +1 -0
  122. package/docker-build/standalone/.next/server/server-reference-manifest.json +5 -0
  123. package/docker-build/standalone/.next/static/_NbfI2TKfapiyxsQgIG3h/_buildManifest.js +11 -0
  124. package/docker-build/standalone/.next/static/_NbfI2TKfapiyxsQgIG3h/_clientMiddlewareManifest.json +1 -0
  125. package/docker-build/standalone/.next/static/_NbfI2TKfapiyxsQgIG3h/_ssgManifest.js +1 -0
  126. package/docker-build/standalone/.next/static/chunks/57d1af92f5dc15fa.js +1 -0
  127. package/docker-build/standalone/.next/static/chunks/6ae71d5e8ea4d1eb.js +1 -0
  128. package/docker-build/standalone/.next/static/chunks/70977d70c9306213.js +1 -0
  129. package/docker-build/standalone/.next/static/chunks/777dac7104fe2a30.js +5 -0
  130. package/docker-build/standalone/.next/static/chunks/a6dad97d9634a72d.js +1 -0
  131. package/docker-build/standalone/.next/static/chunks/a6dad97d9634a72d.js.map +1 -0
  132. package/docker-build/standalone/.next/static/chunks/d6aec49b013224a2.css +3 -0
  133. package/docker-build/standalone/.next/static/chunks/turbopack-0ce517fb6224c1f0.js +4 -0
  134. package/docker-build/standalone/.next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  135. package/docker-build/standalone/.next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  136. package/docker-build/standalone/.next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  137. package/docker-build/standalone/.next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  138. package/docker-build/standalone/.next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  139. package/docker-build/standalone/.next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  140. package/docker-build/standalone/.next/static/media/favicon.0b3bf435.ico +0 -0
  141. package/docker-build/standalone/package.json +26 -0
  142. package/docker-build/standalone/public/file.svg +1 -0
  143. package/docker-build/standalone/public/globe.svg +1 -0
  144. package/docker-build/standalone/public/next.svg +1 -0
  145. package/docker-build/standalone/public/vercel.svg +1 -0
  146. package/docker-build/standalone/public/window.svg +1 -0
  147. package/docker-build/standalone/server.js +38 -0
  148. package/package.json +1 -1
  149. package/src/commands/auth.ts +71 -8
  150. package/src/commands/deploy-sc.ts +182 -0
  151. package/src/commands/deploy.ts +592 -21
  152. package/src/commands/doctor.ts +498 -0
  153. package/src/index.ts +2 -0
  154. package/token.json +69 -0
@@ -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', false)
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
- // Leer versión del package.json
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
- console.log(chalk.gray('Deployment Configuration:'));
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: ${options.push ? chalk.green('Yes') : chalk.yellow('No')}\n`));
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
- // Paso 3: Crear Dockerfile
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
- const builders = execSync('docker buildx ls', { encoding: 'utf-8' });
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 (!builders.includes(builderName)) {
140
- execSync(`docker buildx create --name ${builderName} --use --bootstrap`, { stdio: 'pipe' });
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
- execSync(`docker buildx use ${builderName}`, { stdio: 'pipe' });
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 = options.push
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✅ Deployment completed successfully!'));
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 (options.push) {
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 (options.push) {
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
- if (entry.isDirectory()) {
228
- copyDirectory(srcPath, destPath);
229
- } else {
230
- fs.copyFileSync(srcPath, destPath);
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
  }