hightjs 0.5.3 → 0.5.5

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 (45) hide show
  1. package/dist/adapters/express.d.ts +7 -0
  2. package/dist/adapters/express.js +63 -0
  3. package/dist/adapters/factory.d.ts +23 -0
  4. package/dist/adapters/factory.js +122 -0
  5. package/dist/adapters/fastify.d.ts +25 -0
  6. package/dist/adapters/fastify.js +61 -0
  7. package/dist/adapters/native.d.ts +8 -0
  8. package/dist/adapters/native.js +198 -0
  9. package/dist/api/console.d.ts +94 -0
  10. package/dist/api/console.js +294 -0
  11. package/dist/api/http.d.ts +180 -0
  12. package/dist/api/http.js +469 -0
  13. package/dist/bin/hightjs.d.ts +2 -0
  14. package/dist/bin/hightjs.js +214 -0
  15. package/dist/builder.d.ts +32 -0
  16. package/dist/builder.js +581 -0
  17. package/dist/client/DefaultNotFound.d.ts +1 -0
  18. package/dist/client/DefaultNotFound.js +79 -0
  19. package/dist/client/client.d.ts +3 -0
  20. package/dist/client/client.js +24 -0
  21. package/dist/client/clientRouter.d.ts +58 -0
  22. package/dist/client/clientRouter.js +132 -0
  23. package/dist/client/entry.client.d.ts +1 -0
  24. package/dist/client/entry.client.js +455 -0
  25. package/dist/components/Link.d.ts +7 -0
  26. package/dist/components/Link.js +13 -0
  27. package/dist/global/global.d.ts +117 -0
  28. package/dist/global/global.js +17 -0
  29. package/dist/helpers.d.ts +20 -0
  30. package/dist/helpers.js +583 -0
  31. package/dist/hotReload.d.ts +32 -0
  32. package/dist/hotReload.js +545 -0
  33. package/dist/index.d.ts +18 -0
  34. package/dist/index.js +494 -0
  35. package/dist/loaders.d.ts +1 -0
  36. package/dist/loaders.js +46 -0
  37. package/dist/renderer.d.ts +14 -0
  38. package/dist/renderer.js +380 -0
  39. package/dist/router.d.ts +101 -0
  40. package/dist/router.js +659 -0
  41. package/dist/types/framework.d.ts +37 -0
  42. package/dist/types/framework.js +2 -0
  43. package/dist/types.d.ts +192 -0
  44. package/dist/types.js +2 -0
  45. package/package.json +1 -1
@@ -0,0 +1,469 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HightJSResponse = exports.HightJSRequest = void 0;
4
+ // Input validation and sanitization utilities
5
+ class SecurityUtils {
6
+ static sanitizeHeader(value) {
7
+ if (Array.isArray(value)) {
8
+ return value.map(v => this.sanitizeString(v, this.MAX_HEADER_LENGTH));
9
+ }
10
+ return this.sanitizeString(value, this.MAX_HEADER_LENGTH);
11
+ }
12
+ static sanitizeString(str, maxLength) {
13
+ if (typeof str !== 'string')
14
+ return '';
15
+ // Remove null bytes and control characters except newline/tab
16
+ let clean = str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
17
+ // Limit length
18
+ if (clean.length > maxLength) {
19
+ clean = clean.substring(0, maxLength);
20
+ }
21
+ return clean;
22
+ }
23
+ static isValidURL(url) {
24
+ if (!url || typeof url !== 'string')
25
+ return false;
26
+ if (url.length > this.MAX_URL_LENGTH)
27
+ return false;
28
+ // Basic URL validation - prevent dangerous protocols
29
+ const dangerousProtocols = ['javascript:', 'data:', 'vbscript:', 'file:'];
30
+ const lowerUrl = url.toLowerCase();
31
+ return !dangerousProtocols.some(protocol => lowerUrl.startsWith(protocol));
32
+ }
33
+ static validateContentLength(length) {
34
+ return length >= 0 && length <= this.MAX_BODY_SIZE;
35
+ }
36
+ }
37
+ SecurityUtils.MAX_HEADER_LENGTH = 8192;
38
+ SecurityUtils.MAX_COOKIE_LENGTH = 4096;
39
+ SecurityUtils.MAX_URL_LENGTH = 2048;
40
+ SecurityUtils.MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
41
+ /**
42
+ * Abstração sobre a requisição HTTP de entrada.
43
+ * Funciona com qualquer framework web (Express, Fastify, etc.)
44
+ */
45
+ class HightJSRequest {
46
+ constructor(req) {
47
+ // Validate and sanitize request data
48
+ this._req = this.validateAndSanitizeRequest(req);
49
+ }
50
+ validateAndSanitizeRequest(req) {
51
+ // Validate URL
52
+ if (!SecurityUtils.isValidURL(req.url)) {
53
+ throw new Error('Invalid URL format');
54
+ }
55
+ // Sanitize headers
56
+ const sanitizedHeaders = {};
57
+ for (const [key, value] of Object.entries(req.headers || {})) {
58
+ const cleanKey = SecurityUtils.sanitizeString(key.toLowerCase(), 100);
59
+ if (cleanKey && value) {
60
+ sanitizedHeaders[cleanKey] = SecurityUtils.sanitizeHeader(value);
61
+ }
62
+ }
63
+ // Validate content length
64
+ const contentLength = req.headers['content-length'];
65
+ if (contentLength) {
66
+ const length = parseInt(Array.isArray(contentLength) ? contentLength[0] : contentLength, 10);
67
+ if (!SecurityUtils.validateContentLength(length)) {
68
+ throw new Error('Request too large');
69
+ }
70
+ }
71
+ // Sanitize cookies
72
+ const sanitizedCookies = {};
73
+ for (const [key, value] of Object.entries(req.cookies || {})) {
74
+ const cleanKey = SecurityUtils.sanitizeString(key, 100);
75
+ const cleanValue = SecurityUtils.sanitizeString(value, SecurityUtils.MAX_COOKIE_LENGTH);
76
+ if (cleanKey && cleanValue) {
77
+ sanitizedCookies[cleanKey] = cleanValue;
78
+ }
79
+ }
80
+ return {
81
+ ...req,
82
+ headers: sanitizedHeaders,
83
+ cookies: sanitizedCookies,
84
+ url: SecurityUtils.sanitizeString(req.url, SecurityUtils.MAX_URL_LENGTH)
85
+ };
86
+ }
87
+ /**
88
+ * Retorna o método HTTP da requisição (GET, POST, etc.)
89
+ */
90
+ get method() {
91
+ return this._req.method;
92
+ }
93
+ /**
94
+ * Retorna a URL completa da requisição
95
+ */
96
+ get url() {
97
+ return this._req.url;
98
+ }
99
+ /**
100
+ * Retorna todos os headers da requisição
101
+ */
102
+ get headers() {
103
+ return this._req.headers;
104
+ }
105
+ /**
106
+ * Retorna um header específico com validação
107
+ */
108
+ header(name) {
109
+ if (!name || typeof name !== 'string')
110
+ return undefined;
111
+ const cleanName = SecurityUtils.sanitizeString(name.toLowerCase(), 100);
112
+ return this._req.headers[cleanName];
113
+ }
114
+ /**
115
+ * Retorna todos os query parameters
116
+ */
117
+ get query() {
118
+ return this._req.query || {};
119
+ }
120
+ /**
121
+ * Retorna todos os parâmetros de rota
122
+ */
123
+ get params() {
124
+ return this._req.params || {};
125
+ }
126
+ /**
127
+ * Retorna todos os cookies
128
+ */
129
+ get cookies() {
130
+ return this._req.cookies || {};
131
+ }
132
+ /**
133
+ * Retorna um cookie específico com validação
134
+ */
135
+ cookie(name) {
136
+ if (!name || typeof name !== 'string')
137
+ return undefined;
138
+ const cleanName = SecurityUtils.sanitizeString(name, 100);
139
+ return this._req.cookies?.[cleanName];
140
+ }
141
+ /**
142
+ * Retorna o corpo (body) da requisição, já parseado como JSON com validação
143
+ */
144
+ async json() {
145
+ try {
146
+ const body = this._req.body;
147
+ // Validate JSON structure
148
+ if (typeof body === 'string') {
149
+ // Check for potential JSON bombs
150
+ if (body.length > SecurityUtils.MAX_BODY_SIZE) {
151
+ throw new Error('Request body too large');
152
+ }
153
+ return JSON.parse(body);
154
+ }
155
+ return body;
156
+ }
157
+ catch (error) {
158
+ if (error instanceof SyntaxError) {
159
+ throw new Error('Invalid JSON format');
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ /**
165
+ * Retorna o corpo da requisição como texto
166
+ */
167
+ async text() {
168
+ if (typeof this._req.body === 'string') {
169
+ return this._req.body;
170
+ }
171
+ return JSON.stringify(this._req.body);
172
+ }
173
+ /**
174
+ * Retorna o corpo da requisição como FormData (para uploads)
175
+ */
176
+ async formData() {
177
+ return this._req.body;
178
+ }
179
+ /**
180
+ * Retorna a requisição original do framework
181
+ */
182
+ get raw() {
183
+ return this._req.raw;
184
+ }
185
+ /**
186
+ * Verifica se a requisição tem um content-type específico
187
+ */
188
+ is(type) {
189
+ const contentType = this.header('content-type');
190
+ if (!contentType)
191
+ return false;
192
+ const ct = Array.isArray(contentType) ? contentType[0] : contentType;
193
+ return ct.toLowerCase().includes(type.toLowerCase());
194
+ }
195
+ /**
196
+ * Verifica se a requisição é AJAX/XHR
197
+ */
198
+ get isAjax() {
199
+ const xhr = this.header('x-requested-with');
200
+ return xhr === 'XMLHttpRequest';
201
+ }
202
+ /**
203
+ * Retorna o IP do cliente com validação melhorada
204
+ */
205
+ get ip() {
206
+ // Check X-Forwarded-For with validation
207
+ const forwarded = this.header('x-forwarded-for');
208
+ if (forwarded) {
209
+ const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded;
210
+ const firstIp = ips.split(',')[0].trim();
211
+ // Basic IP validation
212
+ if (this.isValidIP(firstIp)) {
213
+ return firstIp;
214
+ }
215
+ }
216
+ // Check X-Real-IP
217
+ const realIp = this.header('x-real-ip');
218
+ if (realIp) {
219
+ const ip = Array.isArray(realIp) ? realIp[0] : realIp;
220
+ if (this.isValidIP(ip)) {
221
+ return ip;
222
+ }
223
+ }
224
+ return 'unknown';
225
+ }
226
+ isValidIP(ip) {
227
+ if (!ip || typeof ip !== 'string')
228
+ return false;
229
+ // Basic IPv4 validation
230
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
231
+ if (ipv4Regex.test(ip)) {
232
+ const parts = ip.split('.');
233
+ return parts.every(part => {
234
+ const num = parseInt(part, 10);
235
+ return num >= 0 && num <= 255;
236
+ });
237
+ }
238
+ // Basic IPv6 validation (simplified)
239
+ const ipv6Regex = /^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$/;
240
+ return ipv6Regex.test(ip);
241
+ }
242
+ /**
243
+ * Retorna o User-Agent
244
+ */
245
+ get userAgent() {
246
+ const ua = this.header('user-agent');
247
+ return Array.isArray(ua) ? ua[0] : ua;
248
+ }
249
+ }
250
+ exports.HightJSRequest = HightJSRequest;
251
+ /**
252
+ * Abstração para construir a resposta HTTP.
253
+ * Funciona com qualquer framework web (Express, Fastify, etc.)
254
+ */
255
+ class HightJSResponse {
256
+ constructor() {
257
+ this._status = 200;
258
+ this._headers = {};
259
+ this._cookies = [];
260
+ this._body = null;
261
+ this._sent = false;
262
+ }
263
+ /**
264
+ * Define o status HTTP da resposta
265
+ */
266
+ status(code) {
267
+ this._status = code;
268
+ return this;
269
+ }
270
+ /**
271
+ * Define um header da resposta
272
+ */
273
+ header(name, value) {
274
+ this._headers[name] = value;
275
+ return this;
276
+ }
277
+ /**
278
+ * Define múltiplos headers
279
+ */
280
+ headers(headers) {
281
+ Object.assign(this._headers, headers);
282
+ return this;
283
+ }
284
+ /**
285
+ * Define um cookie
286
+ */
287
+ cookie(name, value, options) {
288
+ this._cookies.push({ name, value, options });
289
+ return this;
290
+ }
291
+ /**
292
+ * Remove um cookie
293
+ */
294
+ clearCookie(name, options) {
295
+ this._cookies.push({
296
+ name,
297
+ value: '',
298
+ options: { ...options, expires: new Date(0) }
299
+ });
300
+ return this;
301
+ }
302
+ /**
303
+ * Envia resposta JSON
304
+ */
305
+ json(data) {
306
+ this._headers['Content-Type'] = 'application/json';
307
+ this._body = JSON.stringify(data);
308
+ this._sent = true;
309
+ return this;
310
+ }
311
+ /**
312
+ * Envia resposta de texto
313
+ */
314
+ text(data) {
315
+ this._headers['Content-Type'] = 'text/plain; charset=utf-8';
316
+ this._body = data;
317
+ this._sent = true;
318
+ return this;
319
+ }
320
+ /**
321
+ * Envia resposta HTML
322
+ */
323
+ html(data) {
324
+ this._headers['Content-Type'] = 'text/html; charset=utf-8';
325
+ this._body = data;
326
+ this._sent = true;
327
+ return this;
328
+ }
329
+ /**
330
+ * Envia qualquer tipo de dados
331
+ */
332
+ send(data) {
333
+ this._body = data;
334
+ this._sent = true;
335
+ return this;
336
+ }
337
+ /**
338
+ * Redireciona para uma URL
339
+ */
340
+ redirect(url, status = 302) {
341
+ this._status = status;
342
+ this._headers['Location'] = url;
343
+ this._sent = true;
344
+ return this;
345
+ }
346
+ /**
347
+ * Método interno para aplicar a resposta ao objeto de resposta do framework
348
+ */
349
+ _applyTo(res) {
350
+ // Aplica status
351
+ res.status(this._status);
352
+ // Aplica headers
353
+ Object.entries(this._headers).forEach(([name, value]) => {
354
+ res.header(name, value);
355
+ });
356
+ // Aplica cookies
357
+ this._cookies.forEach(({ name, value, options }) => {
358
+ if (options?.expires && options.expires.getTime() === 0) {
359
+ res.clearCookie(name, options);
360
+ }
361
+ else {
362
+ res.cookie(name, value, options);
363
+ }
364
+ });
365
+ // Handle redirects specifically
366
+ if (this._headers['Location']) {
367
+ res.redirect(this._headers['Location']);
368
+ return;
369
+ }
370
+ // Envia o corpo se foi definido
371
+ if (this._sent && this._body !== null) {
372
+ if (this._headers['Content-Type']?.includes('application/json')) {
373
+ res.json(JSON.parse(this._body));
374
+ }
375
+ else {
376
+ res.send(this._body);
377
+ }
378
+ }
379
+ }
380
+ /**
381
+ * Método de compatibilidade com versão anterior (Express)
382
+ */
383
+ _send(res) {
384
+ // Assume que é Express se tem os métodos específicos
385
+ if (res.set && res.status && res.send) {
386
+ res.set(this._headers).status(this._status);
387
+ this._cookies.forEach(({ name, value, options }) => {
388
+ if (options?.expires && options.expires.getTime() === 0) {
389
+ res.clearCookie(name, options);
390
+ }
391
+ else {
392
+ res.cookie(name, value, options);
393
+ }
394
+ });
395
+ res.send(this._body);
396
+ }
397
+ }
398
+ // === MÉTODOS ESTÁTICOS DE CONVENIÊNCIA ===
399
+ /**
400
+ * Cria uma resposta JSON
401
+ */
402
+ static json(data, options) {
403
+ const response = new HightJSResponse();
404
+ if (options?.status)
405
+ response.status(options.status);
406
+ if (options?.headers)
407
+ response.headers(options.headers);
408
+ return response.json(data);
409
+ }
410
+ /**
411
+ * Cria uma resposta de texto
412
+ */
413
+ static text(data, options) {
414
+ const response = new HightJSResponse();
415
+ if (options?.status)
416
+ response.status(options.status);
417
+ if (options?.headers)
418
+ response.headers(options.headers);
419
+ return response.text(data);
420
+ }
421
+ /**
422
+ * Cria uma resposta HTML
423
+ */
424
+ static html(data, options) {
425
+ const response = new HightJSResponse();
426
+ if (options?.status)
427
+ response.status(options.status);
428
+ if (options?.headers)
429
+ response.headers(options.headers);
430
+ return response.html(data);
431
+ }
432
+ /**
433
+ * Cria um redirecionamento
434
+ */
435
+ static redirect(url, status = 302) {
436
+ return new HightJSResponse().redirect(url, status);
437
+ }
438
+ /**
439
+ * Cria uma resposta 404
440
+ */
441
+ static notFound(message = 'Not Found') {
442
+ return HightJSResponse.text(message, { status: 404 });
443
+ }
444
+ /**
445
+ * Cria uma resposta 500
446
+ */
447
+ static error(message = 'Internal Server Error') {
448
+ return HightJSResponse.text(message, { status: 500 });
449
+ }
450
+ /**
451
+ * Cria uma resposta 400
452
+ */
453
+ static badRequest(message = 'Bad Request') {
454
+ return HightJSResponse.text(message, { status: 400 });
455
+ }
456
+ /**
457
+ * Cria uma resposta 401
458
+ */
459
+ static unauthorized(message = 'Unauthorized') {
460
+ return HightJSResponse.text(message, { status: 401 });
461
+ }
462
+ /**
463
+ * Cria uma resposta 403
464
+ */
465
+ static forbidden(message = 'Forbidden') {
466
+ return HightJSResponse.text(message, { status: 403 });
467
+ }
468
+ }
469
+ exports.HightJSResponse = HightJSResponse;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /*
4
+ * This file is part of the HightJS Project.
5
+ * Copyright (c) 2025 itsmuzin
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ // Registra o ts-node para que o Node.js entenda TypeScript/TSX
20
+ require('ts-node').register();
21
+ // Registra loaders customizados para arquivos markdown, imagens, etc.
22
+ const { registerLoaders } = require('../loaders');
23
+ registerLoaders();
24
+ const { program } = require('commander');
25
+ program
26
+ .version('1.0.0')
27
+ .description('CLI to manage the application.');
28
+ // --- Comando DEV ---
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+ // 'program' já deve estar definido no seu arquivo
32
+ // const { program } = require('commander');
33
+ /**
34
+ * Função centralizada para iniciar a aplicação
35
+ * @param {object} options - Opções vindas do commander
36
+ * @param {boolean} isDev - Define se é modo de desenvolvimento
37
+ */
38
+ function initializeApp(options, isDev) {
39
+ const appOptions = {
40
+ dev: isDev,
41
+ port: options.port,
42
+ hostname: options.hostname,
43
+ framework: 'native',
44
+ ssl: null, // Default
45
+ };
46
+ // 1. Verifica se a flag --ssl foi ativada
47
+ if (options.ssl) {
48
+ const C = require("../api/console");
49
+ const { Levels } = C;
50
+ const Console = C.default;
51
+ const sslDir = path.resolve(process.cwd(), 'certs');
52
+ const keyPath = path.join(sslDir, 'key.pem'); // Padrão 1: key.pem
53
+ const certPath = path.join(sslDir, 'cert.pem'); // Padrão 2: cert.pem
54
+ // (Você pode mudar para 'cert.key' se preferir, apenas ajuste os nomes aqui)
55
+ // 2. Verifica se os arquivos existem
56
+ if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
57
+ appOptions.ssl = {
58
+ key: keyPath,
59
+ cert: certPath
60
+ };
61
+ // 3. Adiciona a porta de redirecionamento (útil para o initNativeServer)
62
+ appOptions.ssl.redirectPort = options.httpRedirectPort || 80;
63
+ }
64
+ else {
65
+ Console.logWithout(Levels.ERROR, null, `Ensure that './certs/key.pem' and './certs/cert.pem' exist.`, `--ssl flag was used, but the files were not found.`);
66
+ process.exit(1); // Encerra o processo com erro
67
+ }
68
+ }
69
+ // 4. Inicia o helper com as opções
70
+ const teste = require("../helpers");
71
+ const t = teste.default(appOptions);
72
+ t.init();
73
+ }
74
+ // --- Comando DEV ---
75
+ program
76
+ .command('dev')
77
+ .description('Starts the application in development mode.')
78
+ .option('-p, --port <number>', 'Specifies the port to run on', '3000')
79
+ .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
80
+ .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
81
+ .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
82
+ .action((options) => {
83
+ initializeApp(options, true); // Chama a função com dev: true
84
+ });
85
+ // --- Comando START (Produção) ---
86
+ program
87
+ .command('start')
88
+ .description('Starts the application in production mode.')
89
+ .option('-p, --port <number>', 'Specifies the port to run on', '3000')
90
+ .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
91
+ .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
92
+ .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
93
+ .action((options) => {
94
+ initializeApp(options, false); // Chama a função com dev: false
95
+ });
96
+ /**
97
+ * Função corrigida para copiar diretórios recursivamente.
98
+ * Ela agora verifica se um item é um arquivo ou um diretório.
99
+ */
100
+ function copyDirRecursive(src, dest) {
101
+ try {
102
+ // Garante que o diretório de destino exista
103
+ fs.mkdirSync(dest, { recursive: true });
104
+ // Usamos { withFileTypes: true } para evitar uma chamada extra de fs.statSync
105
+ const entries = fs.readdirSync(src, { withFileTypes: true });
106
+ for (let entry of entries) {
107
+ const srcPath = path.join(src, entry.name);
108
+ const destPath = path.join(dest, entry.name);
109
+ if (entry.isDirectory()) {
110
+ // Se for um diretório, chama a si mesma (recursão)
111
+ copyDirRecursive(srcPath, destPath);
112
+ }
113
+ else {
114
+ // Se for um arquivo, apenas copia
115
+ fs.copyFileSync(srcPath, destPath);
116
+ }
117
+ }
118
+ }
119
+ catch (error) {
120
+ console.error(`❌ Erro ao copiar ${src} para ${dest}:`, error);
121
+ // Lança o erro para parar o processo de exportação se a cópia falhar
122
+ throw error;
123
+ }
124
+ }
125
+ // --- INÍCIO DO SEU CÓDIGO (AGORA CORRIGIDO) ---
126
+ program
127
+ .command('export')
128
+ .description('Exports the application as static HTML to the "exported" folder.')
129
+ .option('-o, --output <path>', 'Specifies the output directory', 'exported')
130
+ .action(async (options) => {
131
+ const projectDir = process.cwd();
132
+ // Usar path.resolve é mais seguro para garantir um caminho absoluto
133
+ const exportDir = path.resolve(projectDir, options.output);
134
+ console.log('🚀 Starting export...\n');
135
+ try {
136
+ // 1. Cria a pasta exported (limpa se já existir)
137
+ if (fs.existsSync(exportDir)) {
138
+ console.log('🗑️ Cleaning existing export folder...');
139
+ fs.rmSync(exportDir, { recursive: true, force: true });
140
+ }
141
+ fs.mkdirSync(exportDir, { recursive: true });
142
+ console.log('✅ Export folder created\n');
143
+ // 2. Inicializa e prepara o build
144
+ console.log('🔨 Building application...');
145
+ // ATENÇÃO: Ajuste o caminho deste 'require' conforme a estrutura do seu projeto!
146
+ const teste = require("../helpers");
147
+ const app = teste.default({ dev: false, port: 3000, hostname: '0.0.0.0', framework: 'native' });
148
+ await app.prepare();
149
+ console.log('✅ Build complete\n');
150
+ // 3. Copia a pasta .hight para exported (*** CORRIGIDO ***)
151
+ const distDir = path.join(projectDir, '.hight');
152
+ if (fs.existsSync(distDir)) {
153
+ console.log('📦 Copying JavaScript files...');
154
+ const exportDistDir = path.join(exportDir, '.hight');
155
+ // --- Lógica de cópia substituída ---
156
+ // A função copyDirRecursive agora lida com tudo (arquivos e subpastas)
157
+ copyDirRecursive(distDir, exportDistDir);
158
+ // --- Fim da substituição ---
159
+ console.log('✅ JavaScript files copied\n');
160
+ }
161
+ // 4. Copia a pasta public se existir (*** CORRIGIDO ***)
162
+ const publicDir = path.join(projectDir, 'public');
163
+ if (fs.existsSync(publicDir)) {
164
+ console.log('📁 Copying public files...');
165
+ const exportPublicDir = path.join(exportDir, 'public');
166
+ // --- Lógica de cópia substituída ---
167
+ // Reutilizamos a mesma função corrigida
168
+ copyDirRecursive(publicDir, exportPublicDir);
169
+ // --- Fim da substituição ---
170
+ console.log('✅ Public files copied\n');
171
+ }
172
+ // 5. Gera o index.html
173
+ console.log('📝 Generating index.html...');
174
+ // ATENÇÃO: Ajuste os caminhos destes 'requires' conforme a estrutura do seu projeto!
175
+ const { render } = require('../renderer');
176
+ const { loadRoutes, loadLayout, loadNotFound } = require('../router');
177
+ // Carrega as rotas para gerar o HTML
178
+ const userWebDir = path.join(projectDir, 'src', 'web');
179
+ const userWebRoutesDir = path.join(userWebDir, 'routes');
180
+ const routes = loadRoutes(userWebRoutesDir);
181
+ loadLayout(userWebDir);
182
+ loadNotFound(userWebDir);
183
+ // Gera HTML para a rota raiz
184
+ const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
185
+ if (rootRoute) {
186
+ const mockReq = {
187
+ url: '/',
188
+ method: 'GET',
189
+ headers: { host: 'localhost' },
190
+ hwebDev: false,
191
+ hotReloadManager: null
192
+ };
193
+ const html = await render({
194
+ req: mockReq,
195
+ route: rootRoute,
196
+ params: {},
197
+ allRoutes: routes
198
+ });
199
+ const scriptReplaced = html.replace('/_hight/', './.hight/');
200
+ const indexPath = path.join(exportDir, 'index.html');
201
+ fs.writeFileSync(indexPath, scriptReplaced, 'utf8');
202
+ console.log('✅ index.html generated\n');
203
+ }
204
+ console.log('🎉 Export completed successfully!');
205
+ console.log(`📂 Files exported to: ${exportDir}\n`);
206
+ }
207
+ catch (error) {
208
+ // Logar o erro completo (com stack trace) é mais útil
209
+ console.error('❌ Error during export:', error);
210
+ process.exit(1);
211
+ }
212
+ });
213
+ // Faz o "parse" dos argumentos passados na linha de comando
214
+ program.parse(process.argv);