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
@@ -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', false)
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
- // Leer versión del package.json
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: ${options.push ? chalk_1.default.green('Yes') : chalk_1.default.yellow('No')}\n`));
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
- // Paso 3: Crear Dockerfile
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
- const builders = (0, child_process_1.execSync)('docker buildx ls', { encoding: 'utf-8' });
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 (!builders.includes(builderName)) {
165
- (0, child_process_1.execSync)(`docker buildx create --name ${builderName} --use --bootstrap`, { stdio: 'pipe' });
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
- (0, child_process_1.execSync)(`docker buildx use ${builderName}`, { stdio: 'pipe' });
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 = options.push
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✅ Deployment completed successfully!'));
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 (options.push) {
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 (options.push) {
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
- if (entry.isDirectory()) {
249
- copyDirectory(srcPath, destPath);
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
- else {
252
- fs.copyFileSync(srcPath, destPath);
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
  }