entexto-cli 2.0.1 → 2.1.1

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.
@@ -102,7 +102,7 @@ module.exports = async function tunnel(opts) {
102
102
 
103
103
  // Procesar requests del server
104
104
  socket.on('tunnel-request', (payload, cb) => {
105
- const { method, path: reqPath, headers, body, id } = payload;
105
+ const { method, path: reqPath, headers, body, id, streamId } = payload;
106
106
 
107
107
  // Construir URL completa al target local
108
108
  const localUrl = new URL(reqPath, targetUrl);
@@ -126,55 +126,118 @@ module.exports = async function tunnel(opts) {
126
126
  const timestamp = new Date().toLocaleTimeString();
127
127
  process.stdout.write(chalk.gray(` [${timestamp}] `) + chalk.yellow(method.padEnd(6)) + chalk.white(reqPath) + ' ');
128
128
 
129
+ // Detectar si el server soporta streaming (envía streamId)
130
+ const useStreaming = !!streamId;
131
+
129
132
  const localReq = proto.request(reqOpts, (localRes) => {
130
- const chunks = [];
131
- localRes.on('data', (chunk) => chunks.push(chunk));
132
- localRes.on('end', () => {
133
- let bodyBuf = Buffer.concat(chunks);
134
-
135
- // Reescribir el host local → URL pública del túnel en respuestas de texto.
136
- // Esto cubre el caso de Elementor/webpack cuyo __webpack_public_path__
137
- // queda hardcodeado como cmy-rent-car.local (o el dominio local) en el JS.
138
- const ct = (localRes.headers['content-type'] || '').toLowerCase();
139
- const isText = /\b(javascript|css|html|json|xml|svg|text\/)/i.test(ct);
140
- if (isText && publicUrl && bodyBuf.length > 0) {
141
- const localOrigins = [
142
- parsedTarget.origin, // http://cmy-rent-car.local
143
- parsedTarget.origin.replace(/^http:/, 'https:'), // https://cmy-rent-car.local
144
- parsedTarget.host, // cmy-rent-car.local
145
- ];
146
- let text = bodyBuf.toString('utf8');
147
- for (const origin of localOrigins) {
148
- // Reemplazar como origen completo primero, luego como host suelto
149
- text = text.split(origin).join(publicUrl);
150
- }
151
- bodyBuf = Buffer.from(text, 'utf8');
152
- // Corregir Content-Length si venía fijo
153
- localRes.headers['content-length'] = String(bodyBuf.length);
154
- }
133
+ const ct = (localRes.headers['content-type'] || '').toLowerCase();
134
+ const isText = /\b(javascript|css|html|json|xml|svg|text\/)/i.test(ct);
135
+ const status = localRes.statusCode;
155
136
 
156
- // Color del status
157
- const status = localRes.statusCode;
137
+ if (useStreaming) {
138
+ // ── Modo streaming: enviar head + chunks + end ──
158
139
  const statusColor = status < 300 ? chalk.green(status) : status < 400 ? chalk.cyan(status) : chalk.red(status);
159
- console.log(statusColor + chalk.gray(` (${bodyBuf.length}B)`));
140
+ process.stdout.write(statusColor + ' ');
141
+
142
+ // SSE / event-stream y chunked (transfer-encoding) siempre van streaming real
143
+ const isSSE = /event-stream/i.test(ct);
144
+ const isChunked = /chunked/i.test(localRes.headers['transfer-encoding'] || '');
145
+ // Para responses de texto pequeños, acumular para poder reescribir URLs
146
+ // Para binarios, SSE, chunked o responses grandes, hacer streaming directo
147
+ const isSmallText = isText && !isSSE && !isChunked &&
148
+ localRes.headers['content-length'] && parseInt(localRes.headers['content-length']) < 5 * 1024 * 1024;
149
+
150
+ if (isSmallText) {
151
+ // Acumular texto para URL rewriting, luego enviar en un solo stream
152
+ const chunks = [];
153
+ localRes.on('data', (chunk) => chunks.push(chunk));
154
+ localRes.on('end', () => {
155
+ let bodyBuf = Buffer.concat(chunks);
156
+ if (publicUrl && bodyBuf.length > 0) {
157
+ const localOrigins = [
158
+ parsedTarget.origin,
159
+ parsedTarget.origin.replace(/^http:/, 'https:'),
160
+ parsedTarget.host,
161
+ ];
162
+ let text = bodyBuf.toString('utf8');
163
+ for (const origin of localOrigins) {
164
+ text = text.split(origin).join(publicUrl);
165
+ }
166
+ bodyBuf = Buffer.from(text, 'utf8');
167
+ }
168
+ const h = { ...localRes.headers };
169
+ h['content-length'] = String(bodyBuf.length);
170
+ socket.emit('tunnel-stream-head', { streamId, status, headers: h });
171
+ socket.emit('tunnel-stream-chunk', { streamId, data: bodyBuf.toString('base64') });
172
+ socket.emit('tunnel-stream-end', { streamId });
173
+ console.log(chalk.gray(`(${bodyBuf.length}B)`));
174
+ });
175
+ } else {
176
+ // Streaming real: enviar chunks conforme llegan
177
+ const h = { ...localRes.headers };
178
+ delete h['content-length']; // no sabemos el tamaño final
179
+ socket.emit('tunnel-stream-head', { streamId, status, headers: h });
180
+
181
+ let totalBytes = 0;
182
+ localRes.on('data', (chunk) => {
183
+ totalBytes += chunk.length;
184
+ socket.emit('tunnel-stream-chunk', { streamId, data: chunk.toString('base64') });
185
+ });
186
+ localRes.on('end', () => {
187
+ socket.emit('tunnel-stream-end', { streamId });
188
+ console.log(chalk.gray(`(${totalBytes}B streamed)`));
189
+ });
190
+ }
191
+ } else {
192
+ // ── Modo clásico: acumular todo y callback ──
193
+ const chunks = [];
194
+ localRes.on('data', (chunk) => chunks.push(chunk));
195
+ localRes.on('end', () => {
196
+ let bodyBuf = Buffer.concat(chunks);
197
+
198
+ if (isText && publicUrl && bodyBuf.length > 0) {
199
+ const localOrigins = [
200
+ parsedTarget.origin,
201
+ parsedTarget.origin.replace(/^http:/, 'https:'),
202
+ parsedTarget.host,
203
+ ];
204
+ let text = bodyBuf.toString('utf8');
205
+ for (const origin of localOrigins) {
206
+ text = text.split(origin).join(publicUrl);
207
+ }
208
+ bodyBuf = Buffer.from(text, 'utf8');
209
+ localRes.headers['content-length'] = String(bodyBuf.length);
210
+ }
160
211
 
161
- cb({
162
- status: status,
163
- headers: localRes.headers,
164
- body: bodyBuf.toString('base64'),
212
+ const statusColor = status < 300 ? chalk.green(status) : status < 400 ? chalk.cyan(status) : chalk.red(status);
213
+ console.log(statusColor + chalk.gray(` (${bodyBuf.length}B)`));
214
+
215
+ cb({
216
+ status: status,
217
+ headers: localRes.headers,
218
+ body: bodyBuf.toString('base64'),
219
+ });
165
220
  });
166
- });
221
+ }
167
222
  });
168
223
 
169
224
  localReq.on('error', (err) => {
170
225
  console.log(chalk.red('ERR ') + chalk.gray(err.message));
171
- cb({ error: err.message, status: 502 });
226
+ if (useStreaming) {
227
+ socket.emit('tunnel-stream-error', { streamId, error: err.message, status: 502 });
228
+ } else {
229
+ cb({ error: err.message, status: 502 });
230
+ }
172
231
  });
173
232
 
174
- localReq.setTimeout(25000, () => {
233
+ localReq.setTimeout(useStreaming ? 120000 : 25000, () => {
175
234
  localReq.destroy();
176
235
  console.log(chalk.red('TIMEOUT'));
177
- cb({ error: 'Local server timeout', status: 504 });
236
+ if (useStreaming) {
237
+ socket.emit('tunnel-stream-error', { streamId, error: 'Local server timeout', status: 504 });
238
+ } else {
239
+ cb({ error: 'Local server timeout', status: 504 });
240
+ }
178
241
  });
179
242
 
180
243
  if (body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entexto-cli",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "CLI oficial de Entexto — Crea, deploya y gestiona proyectos, dominios, APIs y Live SDK desde tu terminal",
5
5
  "main": "lib/index.js",
6
6
  "bin": {