vatts 1.1.4-alpha.9 → 1.2.0-alpha.2

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 (59) hide show
  1. package/dist/adapters/express.js +2 -3
  2. package/dist/adapters/factory.js +1 -1
  3. package/dist/adapters/fastify.js +2 -3
  4. package/dist/adapters/native.js +6 -7
  5. package/dist/api/console.js +11 -9
  6. package/dist/api/framework.d.ts +2 -0
  7. package/dist/api/framework.js +37 -0
  8. package/dist/api/http.js +11 -11
  9. package/dist/bin/vatts.js +50 -87
  10. package/dist/builder.js +196 -113
  11. package/dist/client/clientRouter.js +1 -1
  12. package/dist/global/global.d.ts +177 -117
  13. package/dist/helpers.d.ts +8 -0
  14. package/dist/helpers.js +8 -3
  15. package/dist/hotReload.d.ts +6 -0
  16. package/dist/hotReload.js +128 -19
  17. package/dist/index.js +29 -12
  18. package/dist/loaders.js +110 -31
  19. package/dist/react/BuildingPage.d.ts +2 -0
  20. package/dist/{client → react}/BuildingPage.js +43 -3
  21. package/dist/react/DefaultNotFound.d.ts +2 -0
  22. package/dist/react/DefaultNotFound.js +242 -0
  23. package/dist/{client → react}/DevIndicator.js +57 -17
  24. package/dist/{client → react}/ErrorModal.js +77 -29
  25. package/dist/{components → react}/Link.d.ts +2 -2
  26. package/dist/{global/global.js → react/Link.js} +15 -0
  27. package/dist/{client → react}/client.d.ts +3 -3
  28. package/dist/{client → react}/client.js +3 -3
  29. package/dist/{client → react}/entry.client.js +10 -8
  30. package/dist/{client → react}/image/Image.js +5 -2
  31. package/dist/react/renderer-react.d.ts +15 -0
  32. package/dist/react/renderer-react.js +371 -0
  33. package/dist/renderer.d.ts +5 -2
  34. package/dist/renderer.js +14 -349
  35. package/dist/router.d.ts +1 -1
  36. package/dist/router.js +14 -8
  37. package/dist/types.d.ts +1 -2
  38. package/dist/vue/App.vue +180 -0
  39. package/dist/vue/BuildingPage.vue +272 -0
  40. package/dist/vue/DefaultNotFound.vue +307 -0
  41. package/dist/vue/DevIndicator.vue +210 -0
  42. package/dist/vue/ErrorModal.vue +301 -0
  43. package/dist/vue/Link.vue +23 -0
  44. package/dist/vue/client.d.ts +7 -0
  45. package/dist/vue/client.js +34 -0
  46. package/dist/vue/entry.client.d.ts +6 -0
  47. package/dist/vue/entry.client.js +99 -0
  48. package/dist/vue/image/Image.vue +107 -0
  49. package/dist/vue/renderer.vue.d.ts +15 -0
  50. package/dist/vue/renderer.vue.js +506 -0
  51. package/package.json +50 -14
  52. package/dist/client/BuildingPage.d.ts +0 -1
  53. package/dist/client/DefaultNotFound.d.ts +0 -1
  54. package/dist/client/DefaultNotFound.js +0 -170
  55. package/dist/components/Link.js +0 -13
  56. /package/dist/{client → react}/DevIndicator.d.ts +0 -0
  57. /package/dist/{client → react}/ErrorModal.d.ts +0 -0
  58. /package/dist/{client → react}/entry.client.d.ts +0 -0
  59. /package/dist/{client → react}/image/Image.d.ts +0 -0
@@ -2,9 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExpressAdapter = void 0;
4
4
  class ExpressAdapter {
5
- constructor() {
6
- this.type = 'express';
7
- }
5
+ type = 'express';
8
6
  parseRequest(req) {
9
7
  return {
10
8
  method: req.method,
@@ -23,6 +21,7 @@ class ExpressAdapter {
23
21
  }
24
22
  exports.ExpressAdapter = ExpressAdapter;
25
23
  class ExpressResponseWrapper {
24
+ res;
26
25
  constructor(res) {
27
26
  this.res = res;
28
27
  }
@@ -42,6 +42,7 @@ const console_1 = __importStar(require("../api/console"));
42
42
  * Factory para criar o adapter correto baseado no framework detectado
43
43
  */
44
44
  class FrameworkAdapterFactory {
45
+ static adapter = null;
45
46
  /**
46
47
  * Detecta automaticamente o framework baseado na requisição/resposta
47
48
  */
@@ -118,4 +119,3 @@ class FrameworkAdapterFactory {
118
119
  }
119
120
  }
120
121
  exports.FrameworkAdapterFactory = FrameworkAdapterFactory;
121
- FrameworkAdapterFactory.adapter = null;
@@ -2,9 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FastifyAdapter = void 0;
4
4
  class FastifyAdapter {
5
- constructor() {
6
- this.type = 'fastify';
7
- }
5
+ type = 'fastify';
8
6
  parseRequest(req) {
9
7
  return {
10
8
  method: req.method,
@@ -23,6 +21,7 @@ class FastifyAdapter {
23
21
  }
24
22
  exports.FastifyAdapter = FastifyAdapter;
25
23
  class FastifyResponseWrapper {
24
+ reply;
26
25
  constructor(reply) {
27
26
  this.reply = reply;
28
27
  }
@@ -24,9 +24,7 @@ function isValidCookieName(name) {
24
24
  return validCookieNameRegex.test(name);
25
25
  }
26
26
  class NativeAdapter {
27
- constructor() {
28
- this.type = 'native';
29
- }
27
+ type = 'native';
30
28
  parseRequest(req) {
31
29
  // URL absoluta é obrigatória para a API WHATWG
32
30
  const fullUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
@@ -70,12 +68,13 @@ class NativeAdapter {
70
68
  }
71
69
  exports.NativeAdapter = NativeAdapter;
72
70
  class NativeResponseWrapper {
71
+ res;
72
+ statusCode = 200;
73
+ headers = {};
74
+ cookiesToSet = []; // Array para lidar corretamente com múltiplos cookies.
75
+ finished = false;
73
76
  constructor(res) {
74
77
  this.res = res;
75
- this.statusCode = 200;
76
- this.headers = {};
77
- this.cookiesToSet = []; // Array para lidar corretamente com múltiplos cookies.
78
- this.finished = false;
79
78
  }
80
79
  get raw() {
81
80
  return this.res;
@@ -27,9 +27,9 @@ const node_readline_1 = __importDefault(require("node:readline"));
27
27
  * o conteúdo da linha.
28
28
  */
29
29
  class DynamicLine {
30
+ // A ID é usada internamente pela classe Console para rastrear esta linha.
31
+ _id = Symbol();
30
32
  constructor(initialContent) {
31
- // A ID é usada internamente pela classe Console para rastrear esta linha.
32
- this._id = Symbol();
33
33
  // Registra esta nova linha na classe Console para que ela seja renderizada.
34
34
  Console['registerDynamicLine'](this._id, initialContent);
35
35
  }
@@ -87,6 +87,10 @@ var Levels;
87
87
  Levels["SUCCESS"] = "SUCCESS";
88
88
  })(Levels || (exports.Levels = Levels = {}));
89
89
  class Console {
90
+ // Armazena o estado de todas as linhas dinâmicas ativas
91
+ static activeLines = [];
92
+ // Quantas linhas foram efetivamente renderizadas na última operação.
93
+ static lastRenderedLines = 0;
90
94
  // --- MÉTODOS PRIVADOS PARA GERENCIAR A RENDERIZAÇÃO ---
91
95
  static redrawDynamicLines() {
92
96
  const stream = process.stdout;
@@ -117,9 +121,11 @@ class Console {
117
121
  node_readline_1.default.cursorTo(stream, 0);
118
122
  node_readline_1.default.clearScreenDown(stream);
119
123
  }
120
- if (!content.endsWith('\n'))
121
- content += '\n';
122
- stream.write(content);
124
+ // MODIFICAÇÃO PRINCIPAL:
125
+ // Substituímos stream.write por console.log aqui.
126
+ // O console.log é interceptado pelos debuggers (VSCode, etc), o stream.write não.
127
+ // Removemos a quebra de linha final (\n$) pois o console.log já adiciona uma automaticamente.
128
+ console.log(content.replace(/\n$/, ''));
123
129
  if (this.activeLines.length > 0) {
124
130
  // ATUALIZADO: Garante que ao redesenhar após um log estático, o formato se mantém
125
131
  stream.write(this.activeLines.map(l => this.formatLog('WAIT', l.content, Colors.FgRed)).join('\n') + '\n');
@@ -299,8 +305,4 @@ class Console {
299
305
  return new DynamicLine(initialContent);
300
306
  }
301
307
  }
302
- // Armazena o estado de todas as linhas dinâmicas ativas
303
- Console.activeLines = [];
304
- // Quantas linhas foram efetivamente renderizadas na última operação.
305
- Console.lastRenderedLines = 0;
306
308
  exports.default = Console;
@@ -0,0 +1,2 @@
1
+ export declare let cachedFramework: 'react' | 'vue' | null;
2
+ export default function detectFramework(projectDir?: string): "react" | "vue";
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.cachedFramework = void 0;
7
+ exports.default = detectFramework;
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ // Variável para armazenar o resultado em memória
11
+ exports.cachedFramework = null;
12
+ function detectFramework(projectDir = process.cwd()) {
13
+ // Se já tivermos um resultado, retorna ele direto sem ler o disco
14
+ if (exports.cachedFramework)
15
+ return exports.cachedFramework;
16
+ try {
17
+ const pkgPath = path_1.default.join(projectDir, 'package.json');
18
+ if (fs_1.default.existsSync(pkgPath)) {
19
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
20
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
21
+ if (deps.react || deps['react-dom']) {
22
+ exports.cachedFramework = 'react';
23
+ return exports.cachedFramework;
24
+ }
25
+ if (deps.vue || deps['nuxt']) {
26
+ exports.cachedFramework = 'vue';
27
+ return exports.cachedFramework;
28
+ }
29
+ }
30
+ }
31
+ catch (e) {
32
+ // Ignora erro de leitura
33
+ }
34
+ // Salva o fallback no cache para evitar re-execução em caso de falha
35
+ exports.cachedFramework = 'react';
36
+ return exports.cachedFramework;
37
+ }
package/dist/api/http.js CHANGED
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VattsResponse = exports.VattsRequest = void 0;
4
4
  // Input validation and sanitization utilities
5
5
  class SecurityUtils {
6
+ static MAX_HEADER_LENGTH = 8192;
7
+ static MAX_COOKIE_LENGTH = 4096;
8
+ static MAX_URL_LENGTH = 2048;
9
+ static MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
6
10
  static sanitizeHeader(value) {
7
11
  if (Array.isArray(value)) {
8
12
  return value.map(v => this.sanitizeString(v, this.MAX_HEADER_LENGTH));
@@ -34,15 +38,13 @@ class SecurityUtils {
34
38
  return length >= 0 && length <= this.MAX_BODY_SIZE;
35
39
  }
36
40
  }
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
41
  /**
42
42
  * Abstração sobre a requisição HTTP de entrada.
43
43
  * Funciona com qualquer framework web (Express, Fastify, etc.)
44
44
  */
45
45
  class VattsRequest {
46
+ /** A requisição genérica parseada pelo adapter */
47
+ _req;
46
48
  constructor(req) {
47
49
  // Validate and sanitize request data
48
50
  this._req = this.validateAndSanitizeRequest(req);
@@ -253,13 +255,11 @@ exports.VattsRequest = VattsRequest;
253
255
  * Funciona com qualquer framework web (Express, Fastify, etc.)
254
256
  */
255
257
  class VattsResponse {
256
- constructor() {
257
- this._status = 200;
258
- this._headers = {};
259
- this._cookies = [];
260
- this._body = null;
261
- this._sent = false;
262
- }
258
+ _status = 200;
259
+ _headers = {};
260
+ _cookies = [];
261
+ _body = null;
262
+ _sent = false;
263
263
  /**
264
264
  * Define o status HTTP da resposta
265
265
  */
package/dist/bin/vatts.js CHANGED
@@ -16,48 +16,40 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
- // Registra o ts-node para que o Node.js entenda TypeScript/TSX
20
19
  require('ts-node').register();
21
- // Registra loaders customizados para arquivos markdown, imagens, etc.
22
20
  const { registerLoaders } = require('../loaders');
23
21
  registerLoaders();
24
22
  const { program } = require('commander');
25
23
  const fs = require('fs');
26
24
  const path = require('path');
27
25
  const { Writable } = require('stream');
28
- // Importa o Console do framework
29
26
  const ConsoleModule = require('../api/console');
27
+ const { loadVattsConfig, config, setConfig } = require("../helpers");
28
+ const { default: detectFramework } = require("../api/framework");
29
+ const { renderAsStream } = require("../renderer");
30
30
  const Console = ConsoleModule.default;
31
31
  const { Levels, Colors } = ConsoleModule;
32
32
  program
33
33
  .version('1.0.0')
34
34
  .description('CLI to manage the application.');
35
35
  // --- Helpers ---
36
- /**
37
- * Função centralizada para iniciar a aplicação
38
- * @param {object} options - Opções vindas do commander
39
- * @param {boolean} isDev - Define se é modo de desenvolvimento
40
- */
41
36
  function initializeApp(options, isDev) {
42
37
  const appOptions = {
43
38
  dev: isDev,
44
39
  port: options.port,
45
40
  hostname: options.hostname,
46
41
  framework: 'native',
47
- ssl: null, // Default
42
+ ssl: null,
48
43
  };
49
- // 1. Verifica se a flag --ssl foi ativada
50
44
  if (options.ssl) {
51
45
  const sslDir = path.resolve(process.cwd(), 'certs');
52
46
  const keyPath = path.join(sslDir, 'key.pem');
53
47
  const certPath = path.join(sslDir, 'cert.pem');
54
- // 2. Verifica se os arquivos existem
55
48
  if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
56
49
  appOptions.ssl = {
57
50
  key: keyPath,
58
51
  cert: certPath
59
52
  };
60
- // 3. Adiciona a porta de redirecionamento
61
53
  appOptions.ssl.redirectPort = options.httpRedirectPort || 80;
62
54
  }
63
55
  else {
@@ -65,14 +57,10 @@ function initializeApp(options, isDev) {
65
57
  process.exit(1);
66
58
  }
67
59
  }
68
- // 4. Inicia o helper com as opções
69
60
  const helperModule = require("../helpers");
70
61
  const helper = helperModule.default(appOptions);
71
62
  helper.init();
72
63
  }
73
- /**
74
- * Função corrigida para copiar diretórios recursivamente.
75
- */
76
64
  function copyDirRecursive(src, dest) {
77
65
  try {
78
66
  fs.mkdirSync(dest, { recursive: true });
@@ -94,136 +82,115 @@ function copyDirRecursive(src, dest) {
94
82
  }
95
83
  }
96
84
  // --- Comandos ---
97
- // Comando DEV
98
85
  program
99
86
  .command('dev')
100
87
  .description('Starts the application in development mode.')
101
88
  .option('-p, --port <number>', 'Specifies the port to run on', '3000')
102
89
  .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
103
- .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
90
+ .option('--ssl', 'Activates HTTPS/SSL mode')
104
91
  .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
105
92
  .action((options) => {
106
93
  initializeApp(options, true);
107
94
  });
108
- // Comando START (Produção)
109
95
  program
110
96
  .command('start')
111
97
  .description('Starts the application in production mode.')
112
98
  .option('-p, --port <number>', 'Specifies the port to run on', '3000')
113
99
  .option('-H, --hostname <string>', 'Specifies the hostname to run on', '0.0.0.0')
114
- .option('--ssl', 'Activates HTTPS/SSL mode (requires ./ssl/key.pem and ./ssl/cert.pem)')
100
+ .option('--ssl', 'Activates HTTPS/SSL mode')
115
101
  .option('--http-redirect-port <number>', 'Port for HTTP->HTTPS redirection', '80')
116
102
  .action((options) => {
117
103
  initializeApp(options, false);
118
104
  });
119
- // Comando EXPORT
120
105
  program
121
106
  .command('export')
122
107
  .description('Exports the application as static HTML to the "exported" folder.')
123
108
  .option('-o, --output <path>', 'Specifies the output directory', 'exported')
124
- .option('--assets-dir <path>', 'Directory (inside output) where the .vatts assets will be written', '.vatts')
109
+ .option('--assets-dir <path>', 'Directory where the .vatts assets will be written', '.vatts')
125
110
  .option('--no-html', 'Do not generate index.html (assets only)')
126
- .option('--output-h-data <file>', 'Write the data-h value from <script id="__vatts_data__" data-h="..."> into this file')
111
+ .option('--output-h-data <file>', 'Write the data-h value into this file')
127
112
  .action(async (options) => {
128
113
  const projectDir = process.cwd();
129
- // Resolve output
130
- const outputInput = typeof options.output === 'string' && options.output.trim().length
131
- ? options.output.trim()
132
- : 'exported';
133
- const exportDir = path.isAbsolute(outputInput)
134
- ? path.resolve(outputInput)
135
- : path.resolve(projectDir, outputInput);
136
- const exportDirResolved = path.resolve(exportDir);
137
- const exportDirRoot = path.parse(exportDirResolved).root;
114
+ const outputInput = (typeof options.output === 'string' && options.output.trim()) || 'exported';
115
+ const exportDirResolved = path.resolve(projectDir, outputInput);
138
116
  const projectDirResolved = path.resolve(projectDir);
139
- if (exportDirResolved === exportDirRoot) {
140
- Console.error(`Refusing to use output directory at drive root: ${exportDirResolved}`);
141
- process.exit(1);
142
- }
143
- const relExportToProject = path.relative(projectDirResolved, exportDirResolved);
144
- if (relExportToProject === '' || relExportToProject === '.' || relExportToProject.startsWith('..')) {
145
- Console.error(`Refusing to export to ${exportDirResolved}. Use a subfolder like "exported" or an explicit path inside the project.`);
117
+ if (exportDirResolved === path.parse(exportDirResolved).root) {
118
+ Console.error(`Refusing to use drive root: ${exportDirResolved}`);
146
119
  process.exit(1);
147
120
  }
148
- // assetsDir
149
- const assetsDirInputRaw = typeof options.assetsDir === 'string' ? options.assetsDir : '.vatts';
150
- const assetsDirInput = assetsDirInputRaw.trim().length ? assetsDirInputRaw.trim() : '.';
121
+ const assetsDirInput = (typeof options.assetsDir === 'string' && options.assetsDir.trim()) || '.vatts';
151
122
  const assetsDirResolved = path.resolve(exportDirResolved, assetsDirInput);
152
123
  const relAssetsToExport = path.relative(exportDirResolved, assetsDirResolved);
153
- if (relAssetsToExport.startsWith('..') || path.isAbsolute(relAssetsToExport)) {
154
- Console.error(`Invalid --assets-dir: must be inside output directory. Received: ${assetsDirInputRaw}`);
155
- process.exit(1);
156
- }
157
- const assetsDirUrl = relAssetsToExport.split(path.sep).join('/').replace(/^\.?\/?/, '');
158
- const assetsBaseHref = '/.' + (assetsDirUrl.length ? assetsDirUrl + '/' : '');
124
+ const assetsBaseHref = '/.' + (relAssetsToExport.split(path.sep).join('/').replace(/^\.?\/?/, '') || '');
159
125
  Console.info('Starting export process...');
160
126
  try {
161
- // 1. Limpa pasta de exportação
127
+ let { loadVattsConfig, setConfig, config } = require("../helpers");
128
+ setConfig(await loadVattsConfig(projectDirResolved, 'production'));
162
129
  if (fs.existsSync(exportDirResolved)) {
163
130
  Console.info('Cleaning existing export folder...');
164
131
  fs.rmSync(exportDirResolved, { recursive: true, force: true });
165
132
  }
166
133
  fs.mkdirSync(exportDirResolved, { recursive: true });
167
- // 2. Build
168
134
  Console.info('Building application...');
169
135
  const helperModule = require("../helpers");
170
- // Usando dev: false para produção
171
136
  const app = helperModule.default({ dev: false, port: 3000, hostname: '0.0.0.0', framework: 'native' });
172
137
  await app.prepare();
173
138
  Console.success('Build complete.');
174
- // 3. Copia JavaScript
175
139
  const distDir = path.join(projectDirResolved, '.vatts');
176
140
  if (fs.existsSync(distDir)) {
177
141
  Console.info('Copying JavaScript files...');
178
- const exportDistDir = assetsDirResolved;
179
- copyDirRecursive(distDir, exportDistDir);
142
+ copyDirRecursive(distDir, assetsDirResolved);
180
143
  }
181
- // 4. Copia Public
182
144
  const publicDir = path.join(projectDirResolved, 'public');
183
145
  if (fs.existsSync(publicDir)) {
184
146
  Console.info('Copying public files...');
185
147
  copyDirRecursive(publicDir, exportDirResolved);
186
148
  }
187
- // 5. Gera index.html
188
- const shouldExtractHData = typeof options.outputHData === 'string' && options.outputHData.trim().length > 0;
189
- const shouldRenderHtml = Boolean(options.html) || shouldExtractHData;
149
+ const shouldExtractHData = !!options.outputHData;
150
+ const shouldRenderHtml = options.html !== false || shouldExtractHData;
190
151
  if (shouldRenderHtml) {
191
- const writeHtmlToDisk = Boolean(options.html);
192
- if (writeHtmlToDisk) {
193
- Console.info('Generating index.html...');
194
- }
195
- else {
196
- Console.info('Rendering HTML for h-data extraction...');
197
- }
152
+ const writeHtmlToDisk = options.html !== false;
153
+ Console.info(writeHtmlToDisk ? 'Generating index.html...' : 'Rendering HTML for h-data extraction...');
154
+ const frameWork = detectFramework(projectDirResolved);
198
155
  const { renderAsStream } = require('../renderer');
199
156
  const { loadRoutes, loadLayout, loadNotFound } = require('../router');
200
157
  const userWebDir = path.join(projectDirResolved, 'src', 'web');
201
158
  const userWebRoutesDir = path.join(userWebDir, 'routes');
159
+ // Recarrega rotas para garantir estado limpo
202
160
  const routes = loadRoutes(userWebRoutesDir);
203
161
  loadLayout(userWebDir);
204
162
  loadNotFound(userWebDir);
205
- const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
206
- if (rootRoute) {
163
+ if (routes.length === 0) {
164
+ Console.warn('No routes found in src/web/routes. Skipping HTML generation.');
165
+ }
166
+ else {
167
+ const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
168
+ let htmlResult = '';
207
169
  const mockReq = {
208
- url: '/',
170
+ url: rootRoute.pattern || '/',
209
171
  method: 'GET',
210
172
  headers: { host: 'localhost' },
211
173
  hwebDev: false,
212
174
  hotReloadManager: null
213
175
  };
214
- let html = '';
215
- let resolveStream;
216
- const streamComplete = new Promise(r => resolveStream = r);
217
176
  const mockRes = new Writable({
218
177
  write(chunk, encoding, callback) {
219
- html += chunk.toString();
178
+ htmlResult += chunk.toString();
220
179
  callback();
221
180
  }
222
181
  });
182
+ // Extensão do mockRes para compatibilidade com o renderer
223
183
  mockRes.setHeader = () => { };
184
+ mockRes.getHeader = () => { };
224
185
  mockRes.statusCode = 200;
225
- mockRes.on('finish', () => {
226
- resolveStream();
186
+ mockRes.end = (chunk) => {
187
+ if (chunk)
188
+ htmlResult += chunk.toString();
189
+ mockRes.emit('finish');
190
+ };
191
+ const streamComplete = new Promise((resolve, reject) => {
192
+ mockRes.on('finish', resolve);
193
+ mockRes.on('error', reject);
227
194
  });
228
195
  await renderAsStream({
229
196
  req: mockReq,
@@ -234,23 +201,19 @@ program
234
201
  });
235
202
  await streamComplete;
236
203
  if (shouldExtractHData) {
237
- const m = html.match(/<script\b[^>]*\bid=["']__vatts_data__["'][^>]*\bdata-h=["']([^"']*)["'][^>]*>/i);
238
- if (!m || typeof m[1] !== 'string') {
239
- throw new Error('Could not find <script id="__vatts_data__" data-h="..."> in rendered HTML.');
204
+ const m = htmlResult.match(/data-h=["']([^"']*)["']/i);
205
+ if (m && m[1]) {
206
+ const outputHDataPath = path.resolve(projectDirResolved, options.outputHData.trim());
207
+ fs.mkdirSync(path.dirname(outputHDataPath), { recursive: true });
208
+ fs.writeFileSync(outputHDataPath, m[1], 'utf8');
209
+ Console.success(`h-data written to: ${path.relative(projectDirResolved, outputHDataPath)}`);
240
210
  }
241
- const hDataValue = m[1];
242
- const outputHDataPathInput = options.outputHData.trim();
243
- const outputHDataPath = path.isAbsolute(outputHDataPathInput)
244
- ? path.resolve(outputHDataPathInput)
245
- : path.resolve(projectDirResolved, outputHDataPathInput);
246
- fs.mkdirSync(path.dirname(outputHDataPath), { recursive: true });
247
- fs.writeFileSync(outputHDataPath, hDataValue, 'utf8');
248
- Console.success(`h-data written to: ${path.relative(projectDirResolved, outputHDataPath)}`);
249
211
  }
250
212
  if (writeHtmlToDisk) {
251
- const scriptReplaced = html.replace(/\/_vatts\//g, assetsBaseHref);
213
+ // Ajusta caminhos dos scripts para serem relativos ao assetsDir
214
+ const finalHtml = htmlResult.replace(/\/_vatts\//g, assetsBaseHref.endsWith('/') ? assetsBaseHref : assetsBaseHref + '/');
252
215
  const indexPath = path.join(exportDirResolved, 'index.html');
253
- fs.writeFileSync(indexPath, scriptReplaced, 'utf8');
216
+ fs.writeFileSync(indexPath, finalHtml, 'utf8');
254
217
  Console.success('index.html generated.');
255
218
  }
256
219
  }