hightjs 1.0.4 → 1.0.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.
@@ -127,18 +127,54 @@ program
127
127
  .command('export')
128
128
  .description('Exports the application as static HTML to the "exported" folder.')
129
129
  .option('-o, --output <path>', 'Specifies the output directory', 'exported')
130
+ .option('--assets-dir <path>', 'Directory (inside output) where the .hight assets will be written', '.hight')
131
+ .option('--no-html', 'Do not generate index.html (assets only)')
130
132
  .action(async (options) => {
131
133
  const projectDir = process.cwd();
132
- // Usar path.resolve é mais seguro para garantir um caminho absoluto
133
- const exportDir = path.resolve(projectDir, options.output);
134
+ // Resolve output:
135
+ // - se vier absoluto, usa como está
136
+ // - se vier relativo, resolve a partir do projeto
137
+ const outputInput = typeof options.output === 'string' && options.output.trim().length
138
+ ? options.output.trim()
139
+ : 'exported';
140
+ const exportDir = path.isAbsolute(outputInput)
141
+ ? path.resolve(outputInput)
142
+ : path.resolve(projectDir, outputInput);
143
+ // Proteções: nunca permitir apagar a raiz do drive (ex: D:\) ou o root do SO (ex: C:\)
144
+ const exportDirResolved = path.resolve(exportDir);
145
+ const exportDirRoot = path.parse(exportDirResolved).root; // ex: 'D:\\'
146
+ const projectDirResolved = path.resolve(projectDir);
147
+ if (exportDirResolved === exportDirRoot) {
148
+ throw new Error(`Refusing to use output directory at drive root: ${exportDirResolved}`);
149
+ }
150
+ // Também evita `--output .` ou `--output ..` que cairia no diretório do projeto (perigoso)
151
+ // Regra: output precisa estar dentro do projeto, ou dentro de uma subpasta do projeto.
152
+ // (mantém comportamento esperado e evita deletar o repo inteiro por acidente)
153
+ const relExportToProject = path.relative(projectDirResolved, exportDirResolved);
154
+ if (relExportToProject === '' || relExportToProject === '.' || relExportToProject.startsWith('..')) {
155
+ throw new Error(`Refusing to export to ${exportDirResolved}. Use a subfolder like "exported" or an explicit path inside the project.`);
156
+ }
157
+ // assetsDir: sempre relativo ao exportDir (evita escrever fora sem querer)
158
+ // Permite usar '.' (raiz do output)
159
+ const assetsDirInputRaw = typeof options.assetsDir === 'string' ? options.assetsDir : '.hight';
160
+ const assetsDirInput = assetsDirInputRaw.trim().length ? assetsDirInputRaw.trim() : '.';
161
+ const assetsDirResolved = path.resolve(exportDirResolved, assetsDirInput);
162
+ const relAssetsToExport = path.relative(exportDirResolved, assetsDirResolved);
163
+ if (relAssetsToExport.startsWith('..') || path.isAbsolute(relAssetsToExport)) {
164
+ throw new Error(`Invalid --assets-dir: must be inside output directory. Received: ${assetsDirInputRaw}`);
165
+ }
166
+ // Normaliza a pasta para uso em URL no HTML
167
+ // Se assets-dir for '.', relAssetsToExport vira '' e assetsBaseHref vira './'
168
+ const assetsDirUrl = relAssetsToExport.split(path.sep).join('/').replace(/^\.?\/?/, '');
169
+ const assetsBaseHref = './' + (assetsDirUrl.length ? assetsDirUrl + '/' : '');
134
170
  console.log('🚀 Starting export...\n');
135
171
  try {
136
172
  // 1. Cria a pasta exported (limpa se já existir)
137
- if (fs.existsSync(exportDir)) {
173
+ if (fs.existsSync(exportDirResolved)) {
138
174
  console.log('🗑️ Cleaning existing export folder...');
139
- fs.rmSync(exportDir, { recursive: true, force: true });
175
+ fs.rmSync(exportDirResolved, { recursive: true, force: true });
140
176
  }
141
- fs.mkdirSync(exportDir, { recursive: true });
177
+ fs.mkdirSync(exportDirResolved, { recursive: true });
142
178
  console.log('✅ Export folder created\n');
143
179
  // 2. Inicializa e prepara o build
144
180
  console.log('🔨 Building application...');
@@ -147,62 +183,62 @@ program
147
183
  const app = teste.default({ dev: false, port: 3000, hostname: '0.0.0.0', framework: 'native' });
148
184
  await app.prepare();
149
185
  console.log('✅ Build complete\n');
150
- // 3. Copia a pasta .hight para exported (*** CORRIGIDO ***)
151
- const distDir = path.join(projectDir, '.hight');
186
+ // 3. Copia a pasta .hight para export (respeitando --assets-dir)
187
+ const distDir = path.join(projectDirResolved, '.hight');
152
188
  if (fs.existsSync(distDir)) {
153
189
  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)
190
+ const exportDistDir = assetsDirResolved;
157
191
  copyDirRecursive(distDir, exportDistDir);
158
- // --- Fim da substituição ---
159
- console.log('✅ JavaScript files copied\n');
192
+ console.log(`✅ JavaScript files copied to: ${path.relative(exportDirResolved, exportDistDir) || '.'}\n`);
160
193
  }
161
- // 4. Copia a pasta public se existir (*** CORRIGIDO ***)
162
- const publicDir = path.join(projectDir, 'public');
194
+ // 4. Copia a pasta public se existir
195
+ const publicDir = path.join(projectDirResolved, 'public');
163
196
  if (fs.existsSync(publicDir)) {
164
197
  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
198
+ const exportPublicDir = path.join(exportDirResolved, 'public');
168
199
  copyDirRecursive(publicDir, exportPublicDir);
169
- // --- Fim da substituição ---
170
200
  console.log('✅ Public files copied\n');
171
201
  }
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');
202
+ // 5. Gera o index.html (opcional)
203
+ if (options.html) {
204
+ console.log('📝 Generating index.html...');
205
+ // ATENÇÃO: Ajuste os caminhos destes 'requires' conforme a estrutura do seu projeto!
206
+ const { render } = require('../renderer');
207
+ const { loadRoutes, loadLayout, loadNotFound } = require('../router');
208
+ // Carrega as rotas para gerar o HTML
209
+ const userWebDir = path.join(projectDirResolved, 'src', 'web');
210
+ const userWebRoutesDir = path.join(userWebDir, 'routes');
211
+ const routes = loadRoutes(userWebRoutesDir);
212
+ loadLayout(userWebDir);
213
+ loadNotFound(userWebDir);
214
+ // Gera HTML para a rota raiz
215
+ const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
216
+ if (rootRoute) {
217
+ const mockReq = {
218
+ url: '/',
219
+ method: 'GET',
220
+ headers: { host: 'localhost' },
221
+ hwebDev: false,
222
+ hotReloadManager: null
223
+ };
224
+ const html = await render({
225
+ req: mockReq,
226
+ route: rootRoute,
227
+ params: {},
228
+ allRoutes: routes
229
+ });
230
+ // Mantém o padrão /_hight/ mas permite trocar o destino no export
231
+ const scriptReplaced = html.replace(/\/_hight\//g, assetsBaseHref);
232
+ const indexPath = path.join(exportDirResolved, 'index.html');
233
+ fs.writeFileSync(indexPath, scriptReplaced, 'utf8');
234
+ console.log('✅ index.html generated\n');
235
+ }
236
+ }
237
+ else {
238
+ console.log('⏭️ Skipping index.html generation (--no-html)\n');
203
239
  }
204
240
  console.log('🎉 Export completed successfully!');
205
- console.log(`📂 Files exported to: ${exportDir}\n`);
241
+ console.log(`📂 Files exported to: ${exportDirResolved}\n`);
206
242
  }
207
243
  catch (error) {
208
244
  // Logar o erro completo (com stack trace) é mais útil
package/dist/builder.js CHANGED
@@ -143,6 +143,58 @@ const npmDependenciesPlugin = {
143
143
  });
144
144
  }
145
145
  };
146
+ /**
147
+ * Plugin para resolver aliases do tsconfig.json (como @/)
148
+ */
149
+ const aliasResolvePlugin = {
150
+ name: 'alias-resolve',
151
+ setup(build) {
152
+ const projectDir = process.cwd();
153
+ const tsconfigPath = path.join(projectDir, 'tsconfig.json');
154
+ let aliases = {};
155
+ // Lê o tsconfig.json para pegar os paths
156
+ if (fs.existsSync(tsconfigPath)) {
157
+ try {
158
+ const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
159
+ // Remove comentários do JSON (tsconfig pode ter comentários)
160
+ const jsonContent = tsconfigContent.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
161
+ const tsconfig = JSON.parse(jsonContent);
162
+ if (tsconfig.compilerOptions?.paths) {
163
+ const baseUrl = tsconfig.compilerOptions.baseUrl || '.';
164
+ const basePath = path.resolve(projectDir, baseUrl);
165
+ // Converte os paths do tsconfig para aliases
166
+ for (const [alias, paths] of Object.entries(tsconfig.compilerOptions.paths)) {
167
+ if (Array.isArray(paths) && paths.length > 0) {
168
+ // Remove o /* do final do alias e do path
169
+ const cleanAlias = alias.replace(/\/\*$/, '');
170
+ const cleanPath = paths[0].replace(/\/\*$/, '');
171
+ aliases[cleanAlias] = path.resolve(basePath, cleanPath);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ catch (error) {
177
+ Console.warn('Error reading tsconfig.json for aliases:', error.message);
178
+ }
179
+ }
180
+ // Se tem aliases configurados, resolve eles
181
+ if (Object.keys(aliases).length > 0) {
182
+ build.onResolve({ filter: /.*/ }, (args) => {
183
+ // Verifica se o import começa com algum alias
184
+ for (const [alias, aliasPath] of Object.entries(aliases)) {
185
+ if (args.path === alias || args.path.startsWith(alias + '/')) {
186
+ const relativePath = args.path.slice(alias.length);
187
+ const resolvedPath = path.join(aliasPath, relativePath);
188
+ return {
189
+ path: resolvedPath
190
+ };
191
+ }
192
+ }
193
+ return null;
194
+ });
195
+ }
196
+ }
197
+ };
146
198
  /**
147
199
  * Plugin para garantir que React seja corretamente resolvido
148
200
  */
@@ -368,7 +420,7 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
368
420
  outdir: outdir,
369
421
  loader: { '.js': 'js', '.ts': 'tsx' },
370
422
  external: nodeBuiltIns,
371
- plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
423
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
372
424
  format: 'esm', // ESM suporta melhor o code splitting
373
425
  jsx: 'automatic',
374
426
  define: {
@@ -429,7 +481,7 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
429
481
  outdir: outdir,
430
482
  loader: { '.js': 'js', '.ts': 'tsx' },
431
483
  external: nodeBuiltIns,
432
- plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin, markdownPlugin, assetsPlugin],
484
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin, markdownPlugin, assetsPlugin],
433
485
  format: 'esm',
434
486
  jsx: 'automatic',
435
487
  define: {
@@ -473,7 +525,7 @@ async function build(entryPoint, outfile, isProduction = false) {
473
525
  outfile: outfile,
474
526
  loader: { '.js': 'js', '.ts': 'tsx' },
475
527
  external: nodeBuiltIns,
476
- plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
528
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
477
529
  format: 'iife',
478
530
  globalName: 'HwebApp',
479
531
  jsx: 'automatic',
@@ -535,6 +587,7 @@ async function watch(entryPoint, outfile, hotReloadManager = null) {
535
587
  outfile: outfile,
536
588
  loader: { '.js': 'js', '.ts': 'tsx' },
537
589
  external: nodeBuiltIns,
590
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin, markdownPlugin, assetsPlugin],
538
591
  format: 'iife',
539
592
  globalName: 'HwebApp',
540
593
  jsx: 'automatic',
@@ -160,7 +160,6 @@ function App({ componentMap, routes, initialComponentPath, initialParams, layout
160
160
  if (match) {
161
161
  setCurrentPageComponent(() => componentMap[match.componentPath]);
162
162
  setParams(match.params);
163
- // setar o titulo da página se necessário
164
163
  }
165
164
  else {
166
165
  // Se não encontrou rota, define como null para mostrar 404
package/dist/loaders.js CHANGED
@@ -16,11 +16,103 @@
16
16
  * limitations under the License.
17
17
  */
18
18
  const fs = require('fs');
19
+ const path = require('path');
20
+ const Module = require('module');
21
+ /**
22
+ * Carrega e processa o tsconfig.json para obter os aliases
23
+ */
24
+ function loadTsConfigAliases() {
25
+ const projectDir = process.cwd();
26
+ const tsconfigPath = path.join(projectDir, 'tsconfig.json');
27
+ const aliases = {};
28
+ if (fs.existsSync(tsconfigPath)) {
29
+ try {
30
+ const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
31
+ // Remove comentários JSONC de forma mais robusta
32
+ let jsonContent = tsconfigContent
33
+ // Remove comentários de linha // (mas não em strings)
34
+ .replace(/(?:^|\s)\/\/.*$/gm, '')
35
+ // Remove comentários de bloco /* */
36
+ .replace(/\/\*[\s\S]*?\*\//g, '')
37
+ // Remove vírgulas finais (trailing commas) que JSON não aceita
38
+ .replace(/,(\s*[}\]])/g, '$1');
39
+ const tsconfig = JSON.parse(jsonContent);
40
+ if (tsconfig.compilerOptions?.paths) {
41
+ const baseUrl = tsconfig.compilerOptions.baseUrl || '.';
42
+ const basePath = path.resolve(projectDir, baseUrl);
43
+ // Converte os paths do tsconfig para aliases
44
+ for (const [alias, paths] of Object.entries(tsconfig.compilerOptions.paths)) {
45
+ if (Array.isArray(paths) && paths.length > 0) {
46
+ // Remove o /* do final
47
+ const cleanAlias = alias.replace(/\/\*$/, '');
48
+ const cleanPath = paths[0].replace(/\/\*$/, '');
49
+ aliases[cleanAlias] = path.resolve(basePath, cleanPath);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ catch (error) {
55
+ // Silenciosamente ignora erros de parsing do tsconfig
56
+ }
57
+ }
58
+ return aliases;
59
+ }
19
60
  /**
20
61
  * Registra loaders customizados para Node.js
21
62
  * Permite importar arquivos não-JS diretamente no servidor
22
63
  */
23
64
  function registerLoaders() {
65
+ // Registra resolver de aliases do tsconfig.json
66
+ const aliases = loadTsConfigAliases();
67
+ if (Object.keys(aliases).length > 0) {
68
+ // Guarda referência ao _resolveFilename atual (pode já ter sido modificado por source-map-support)
69
+ const originalResolveFilename = Module._resolveFilename;
70
+ Module._resolveFilename = function (request, parent, isMain, options) {
71
+ // Verifica se o request começa com algum alias
72
+ for (const [alias, aliasPath] of Object.entries(aliases)) {
73
+ if (request === alias || request.startsWith(alias + '/')) {
74
+ const relativePath = request.slice(alias.length);
75
+ const resolvedPath = path.join(aliasPath, relativePath);
76
+ // Tenta resolver com diferentes extensões
77
+ const extensions = ['.tsx', '.ts', '.jsx', '.js'];
78
+ // Primeiro tenta o caminho exato
79
+ if (fs.existsSync(resolvedPath)) {
80
+ const stats = fs.statSync(resolvedPath);
81
+ if (stats.isFile()) {
82
+ // Retorna o caminho resolvido diretamente
83
+ request = resolvedPath;
84
+ break;
85
+ }
86
+ }
87
+ // Tenta com extensões
88
+ let resolved = false;
89
+ for (const ext of extensions) {
90
+ const pathWithExt = resolvedPath + ext;
91
+ if (fs.existsSync(pathWithExt)) {
92
+ request = pathWithExt;
93
+ resolved = true;
94
+ break;
95
+ }
96
+ }
97
+ if (resolved)
98
+ break;
99
+ // Tenta como diretório com index
100
+ for (const ext of extensions) {
101
+ const indexPath = path.join(resolvedPath, 'index' + ext);
102
+ if (fs.existsSync(indexPath)) {
103
+ request = indexPath;
104
+ resolved = true;
105
+ break;
106
+ }
107
+ }
108
+ if (resolved)
109
+ break;
110
+ }
111
+ }
112
+ // Chama o resolver original (que pode ser do source-map-support)
113
+ return originalResolveFilename.call(this, request, parent, isMain, options);
114
+ };
115
+ }
24
116
  // Loader para arquivos Markdown (.md)
25
117
  require.extensions['.md'] = function (module, filename) {
26
118
  const content = fs.readFileSync(filename, 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hightjs",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "HightJS is a high-level framework for building web applications with ease and speed. It provides a robust set of tools and features to streamline development and enhance productivity.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -148,20 +148,64 @@ program
148
148
  .command('export')
149
149
  .description('Exports the application as static HTML to the "exported" folder.')
150
150
  .option('-o, --output <path>', 'Specifies the output directory', 'exported')
151
+ .option('--assets-dir <path>', 'Directory (inside output) where the .hight assets will be written', '.hight')
152
+ .option('--no-html', 'Do not generate index.html (assets only)')
151
153
  .action(async (options) => {
152
154
  const projectDir = process.cwd();
153
- // Usar path.resolve é mais seguro para garantir um caminho absoluto
154
- const exportDir = path.resolve(projectDir, options.output);
155
+
156
+ // Resolve output:
157
+ // - se vier absoluto, usa como está
158
+ // - se vier relativo, resolve a partir do projeto
159
+ const outputInput = typeof options.output === 'string' && options.output.trim().length
160
+ ? options.output.trim()
161
+ : 'exported';
162
+ const exportDir = path.isAbsolute(outputInput)
163
+ ? path.resolve(outputInput)
164
+ : path.resolve(projectDir, outputInput);
165
+
166
+ // Proteções: nunca permitir apagar a raiz do drive (ex: D:\) ou o root do SO (ex: C:\)
167
+ const exportDirResolved = path.resolve(exportDir);
168
+ const exportDirRoot = path.parse(exportDirResolved).root; // ex: 'D:\\'
169
+ const projectDirResolved = path.resolve(projectDir);
170
+
171
+ if (exportDirResolved === exportDirRoot) {
172
+ throw new Error(`Refusing to use output directory at drive root: ${exportDirResolved}`);
173
+ }
174
+
175
+ // Também evita `--output .` ou `--output ..` que cairia no diretório do projeto (perigoso)
176
+ // Regra: output precisa estar dentro do projeto, ou dentro de uma subpasta do projeto.
177
+ // (mantém comportamento esperado e evita deletar o repo inteiro por acidente)
178
+ const relExportToProject = path.relative(projectDirResolved, exportDirResolved);
179
+ if (relExportToProject === '' || relExportToProject === '.' || relExportToProject.startsWith('..')) {
180
+ throw new Error(
181
+ `Refusing to export to ${exportDirResolved}. Use a subfolder like "exported" or an explicit path inside the project.`
182
+ );
183
+ }
184
+
185
+ // assetsDir: sempre relativo ao exportDir (evita escrever fora sem querer)
186
+ // Permite usar '.' (raiz do output)
187
+ const assetsDirInputRaw = typeof options.assetsDir === 'string' ? options.assetsDir : '.hight';
188
+ const assetsDirInput = assetsDirInputRaw.trim().length ? assetsDirInputRaw.trim() : '.';
189
+ const assetsDirResolved = path.resolve(exportDirResolved, assetsDirInput);
190
+ const relAssetsToExport = path.relative(exportDirResolved, assetsDirResolved);
191
+ if (relAssetsToExport.startsWith('..') || path.isAbsolute(relAssetsToExport)) {
192
+ throw new Error(`Invalid --assets-dir: must be inside output directory. Received: ${assetsDirInputRaw}`);
193
+ }
194
+
195
+ // Normaliza a pasta para uso em URL no HTML
196
+ // Se assets-dir for '.', relAssetsToExport vira '' e assetsBaseHref vira './'
197
+ const assetsDirUrl = relAssetsToExport.split(path.sep).join('/').replace(/^\.?\/?/, '');
198
+ const assetsBaseHref = './' + (assetsDirUrl.length ? assetsDirUrl + '/' : '');
155
199
 
156
200
  console.log('🚀 Starting export...\n');
157
201
 
158
202
  try {
159
203
  // 1. Cria a pasta exported (limpa se já existir)
160
- if (fs.existsSync(exportDir)) {
204
+ if (fs.existsSync(exportDirResolved)) {
161
205
  console.log('🗑️ Cleaning existing export folder...');
162
- fs.rmSync(exportDir, { recursive: true, force: true });
206
+ fs.rmSync(exportDirResolved, { recursive: true, force: true });
163
207
  }
164
- fs.mkdirSync(exportDir, { recursive: true });
208
+ fs.mkdirSync(exportDirResolved, { recursive: true });
165
209
  console.log('✅ Export folder created\n');
166
210
 
167
211
  // 2. Inicializa e prepara o build
@@ -172,74 +216,74 @@ program
172
216
  await app.prepare();
173
217
  console.log('✅ Build complete\n');
174
218
 
175
- // 3. Copia a pasta .hight para exported (*** CORRIGIDO ***)
176
- const distDir = path.join(projectDir, '.hight');
219
+ // 3. Copia a pasta .hight para export (respeitando --assets-dir)
220
+ const distDir = path.join(projectDirResolved, '.hight');
177
221
  if (fs.existsSync(distDir)) {
178
222
  console.log('📦 Copying JavaScript files...');
179
- const exportDistDir = path.join(exportDir, '.hight');
223
+ const exportDistDir = assetsDirResolved;
180
224
 
181
- // --- Lógica de cópia substituída ---
182
- // A função copyDirRecursive agora lida com tudo (arquivos e subpastas)
183
225
  copyDirRecursive(distDir, exportDistDir);
184
- // --- Fim da substituição ---
185
226
 
186
- console.log('✅ JavaScript files copied\n');
227
+ console.log(`✅ JavaScript files copied to: ${path.relative(exportDirResolved, exportDistDir) || '.'}\n`);
187
228
  }
188
229
 
189
- // 4. Copia a pasta public se existir (*** CORRIGIDO ***)
190
- const publicDir = path.join(projectDir, 'public');
230
+ // 4. Copia a pasta public se existir
231
+ const publicDir = path.join(projectDirResolved, 'public');
191
232
  if (fs.existsSync(publicDir)) {
192
233
  console.log('📁 Copying public files...');
193
- const exportPublicDir = path.join(exportDir, 'public');
234
+ const exportPublicDir = path.join(exportDirResolved, 'public');
194
235
 
195
- // --- Lógica de cópia substituída ---
196
- // Reutilizamos a mesma função corrigida
197
236
  copyDirRecursive(publicDir, exportPublicDir);
198
- // --- Fim da substituição ---
199
237
 
200
238
  console.log('✅ Public files copied\n');
201
239
  }
202
240
 
203
- // 5. Gera o index.html
204
- console.log('📝 Generating index.html...');
205
- // ATENÇÃO: Ajuste os caminhos destes 'requires' conforme a estrutura do seu projeto!
206
- const { render } = require('../renderer');
207
- const { loadRoutes, loadLayout, loadNotFound } = require('../router');
208
-
209
- // Carrega as rotas para gerar o HTML
210
- const userWebDir = path.join(projectDir, 'src', 'web');
211
- const userWebRoutesDir = path.join(userWebDir, 'routes');
212
-
213
- const routes = loadRoutes(userWebRoutesDir);
214
- loadLayout(userWebDir);
215
- loadNotFound(userWebDir);
216
-
217
- // Gera HTML para a rota raiz
218
- const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
219
-
220
- if (rootRoute) {
221
- const mockReq = {
222
- url: '/',
223
- method: 'GET',
224
- headers: { host: 'localhost' },
225
- hwebDev: false,
226
- hotReloadManager: null
227
- };
228
-
229
- const html = await render({
230
- req: mockReq,
231
- route: rootRoute,
232
- params: {},
233
- allRoutes: routes
234
- });
235
- const scriptReplaced = html.replace('/_hight/', './.hight/');
236
- const indexPath = path.join(exportDir, 'index.html');
237
- fs.writeFileSync(indexPath, scriptReplaced, 'utf8');
238
- console.log('✅ index.html generated\n');
241
+ // 5. Gera o index.html (opcional)
242
+ if (options.html) {
243
+ console.log('📝 Generating index.html...');
244
+ // ATENÇÃO: Ajuste os caminhos destes 'requires' conforme a estrutura do seu projeto!
245
+ const { render } = require('../renderer');
246
+ const { loadRoutes, loadLayout, loadNotFound } = require('../router');
247
+
248
+ // Carrega as rotas para gerar o HTML
249
+ const userWebDir = path.join(projectDirResolved, 'src', 'web');
250
+ const userWebRoutesDir = path.join(userWebDir, 'routes');
251
+
252
+ const routes = loadRoutes(userWebRoutesDir);
253
+ loadLayout(userWebDir);
254
+ loadNotFound(userWebDir);
255
+
256
+ // Gera HTML para a rota raiz
257
+ const rootRoute = routes.find(r => r.pattern === '/') || routes[0];
258
+
259
+ if (rootRoute) {
260
+ const mockReq = {
261
+ url: '/',
262
+ method: 'GET',
263
+ headers: { host: 'localhost' },
264
+ hwebDev: false,
265
+ hotReloadManager: null
266
+ };
267
+
268
+ const html = await render({
269
+ req: mockReq,
270
+ route: rootRoute,
271
+ params: {},
272
+ allRoutes: routes
273
+ });
274
+
275
+ // Mantém o padrão /_hight/ mas permite trocar o destino no export
276
+ const scriptReplaced = html.replace(/\/_hight\//g, assetsBaseHref);
277
+ const indexPath = path.join(exportDirResolved, 'index.html');
278
+ fs.writeFileSync(indexPath, scriptReplaced, 'utf8');
279
+ console.log('✅ index.html generated\n');
280
+ }
281
+ } else {
282
+ console.log('⏭️ Skipping index.html generation (--no-html)\n');
239
283
  }
240
284
 
241
285
  console.log('🎉 Export completed successfully!');
242
- console.log(`📂 Files exported to: ${exportDir}\n`);
286
+ console.log(`📂 Files exported to: ${exportDirResolved}\n`);
243
287
 
244
288
  } catch (error) {
245
289
  // Logar o erro completo (com stack trace) é mais útil
package/src/builder.js CHANGED
@@ -153,6 +153,64 @@ const npmDependenciesPlugin = {
153
153
  }
154
154
  };
155
155
 
156
+ /**
157
+ * Plugin para resolver aliases do tsconfig.json (como @/)
158
+ */
159
+ const aliasResolvePlugin = {
160
+ name: 'alias-resolve',
161
+ setup(build) {
162
+ const projectDir = process.cwd();
163
+ const tsconfigPath = path.join(projectDir, 'tsconfig.json');
164
+
165
+ let aliases = {};
166
+
167
+ // Lê o tsconfig.json para pegar os paths
168
+ if (fs.existsSync(tsconfigPath)) {
169
+ try {
170
+ const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
171
+ // Remove comentários do JSON (tsconfig pode ter comentários)
172
+ const jsonContent = tsconfigContent.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
173
+ const tsconfig = JSON.parse(jsonContent);
174
+
175
+ if (tsconfig.compilerOptions?.paths) {
176
+ const baseUrl = tsconfig.compilerOptions.baseUrl || '.';
177
+ const basePath = path.resolve(projectDir, baseUrl);
178
+
179
+ // Converte os paths do tsconfig para aliases
180
+ for (const [alias, paths] of Object.entries(tsconfig.compilerOptions.paths)) {
181
+ if (Array.isArray(paths) && paths.length > 0) {
182
+ // Remove o /* do final do alias e do path
183
+ const cleanAlias = alias.replace(/\/\*$/, '');
184
+ const cleanPath = paths[0].replace(/\/\*$/, '');
185
+ aliases[cleanAlias] = path.resolve(basePath, cleanPath);
186
+ }
187
+ }
188
+ }
189
+ } catch (error) {
190
+ Console.warn('Error reading tsconfig.json for aliases:', error.message);
191
+ }
192
+ }
193
+
194
+ // Se tem aliases configurados, resolve eles
195
+ if (Object.keys(aliases).length > 0) {
196
+ build.onResolve({ filter: /.*/ }, (args) => {
197
+ // Verifica se o import começa com algum alias
198
+ for (const [alias, aliasPath] of Object.entries(aliases)) {
199
+ if (args.path === alias || args.path.startsWith(alias + '/')) {
200
+ const relativePath = args.path.slice(alias.length);
201
+ const resolvedPath = path.join(aliasPath, relativePath);
202
+
203
+ return {
204
+ path: resolvedPath
205
+ };
206
+ }
207
+ }
208
+ return null;
209
+ });
210
+ }
211
+ }
212
+ };
213
+
156
214
  /**
157
215
  * Plugin para garantir que React seja corretamente resolvido
158
216
  */
@@ -411,7 +469,7 @@ async function buildWithChunks(entryPoint, outdir, isProduction = false) {
411
469
  outdir: outdir,
412
470
  loader: { '.js': 'js', '.ts': 'tsx' },
413
471
  external: nodeBuiltIns,
414
- plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
472
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
415
473
  format: 'esm', // ESM suporta melhor o code splitting
416
474
  jsx: 'automatic',
417
475
  define: {
@@ -473,7 +531,7 @@ async function watchWithChunks(entryPoint, outdir, hotReloadManager = null) {
473
531
  outdir: outdir,
474
532
  loader: { '.js': 'js', '.ts': 'tsx' },
475
533
  external: nodeBuiltIns,
476
- plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin, markdownPlugin, assetsPlugin],
534
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin, markdownPlugin, assetsPlugin],
477
535
  format: 'esm',
478
536
  jsx: 'automatic',
479
537
  define: {
@@ -519,7 +577,7 @@ async function build(entryPoint, outfile, isProduction = false) {
519
577
  outfile: outfile,
520
578
  loader: { '.js': 'js', '.ts': 'tsx' },
521
579
  external: nodeBuiltIns,
522
- plugins: [postcssPlugin, npmDependenciesPlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
580
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, markdownPlugin, assetsPlugin],
523
581
  format: 'iife',
524
582
  globalName: 'HwebApp',
525
583
  jsx: 'automatic',
@@ -582,6 +640,7 @@ async function watch(entryPoint, outfile, hotReloadManager = null) {
582
640
  outfile: outfile,
583
641
  loader: { '.js': 'js', '.ts': 'tsx' },
584
642
  external: nodeBuiltIns,
643
+ plugins: [postcssPlugin, npmDependenciesPlugin, aliasResolvePlugin, reactResolvePlugin, hmrPlugin, buildCompletePlugin, markdownPlugin, assetsPlugin],
585
644
  format: 'iife',
586
645
  globalName: 'HwebApp',
587
646
  jsx: 'automatic',
@@ -152,8 +152,6 @@ function App({ componentMap, routes, initialComponentPath, initialParams, layout
152
152
  if (match) {
153
153
  setCurrentPageComponent(() => componentMap[match.componentPath]);
154
154
  setParams(match.params);
155
- // setar o titulo da página se necessário
156
-
157
155
  } else {
158
156
  // Se não encontrou rota, define como null para mostrar 404
159
157
  setCurrentPageComponent(null);
package/src/loaders.js CHANGED
@@ -16,12 +16,119 @@
16
16
  */
17
17
 
18
18
  const fs = require('fs');
19
+ const path = require('path');
20
+ const Module = require('module');
21
+
22
+ /**
23
+ * Carrega e processa o tsconfig.json para obter os aliases
24
+ */
25
+ function loadTsConfigAliases() {
26
+ const projectDir = process.cwd();
27
+ const tsconfigPath = path.join(projectDir, 'tsconfig.json');
28
+
29
+ const aliases = {};
30
+
31
+ if (fs.existsSync(tsconfigPath)) {
32
+ try {
33
+ const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf8');
34
+
35
+ // Remove comentários JSONC de forma mais robusta
36
+ let jsonContent = tsconfigContent
37
+ // Remove comentários de linha // (mas não em strings)
38
+ .replace(/(?:^|\s)\/\/.*$/gm, '')
39
+ // Remove comentários de bloco /* */
40
+ .replace(/\/\*[\s\S]*?\*\//g, '')
41
+ // Remove vírgulas finais (trailing commas) que JSON não aceita
42
+ .replace(/,(\s*[}\]])/g, '$1');
43
+
44
+ const tsconfig = JSON.parse(jsonContent);
45
+
46
+ if (tsconfig.compilerOptions?.paths) {
47
+ const baseUrl = tsconfig.compilerOptions.baseUrl || '.';
48
+ const basePath = path.resolve(projectDir, baseUrl);
49
+
50
+ // Converte os paths do tsconfig para aliases
51
+ for (const [alias, paths] of Object.entries(tsconfig.compilerOptions.paths)) {
52
+ if (Array.isArray(paths) && paths.length > 0) {
53
+ // Remove o /* do final
54
+ const cleanAlias = alias.replace(/\/\*$/, '');
55
+ const cleanPath = paths[0].replace(/\/\*$/, '');
56
+ aliases[cleanAlias] = path.resolve(basePath, cleanPath);
57
+ }
58
+ }
59
+ }
60
+ } catch (error) {
61
+ // Silenciosamente ignora erros de parsing do tsconfig
62
+ }
63
+ }
64
+
65
+ return aliases;
66
+ }
19
67
 
20
68
  /**
21
69
  * Registra loaders customizados para Node.js
22
70
  * Permite importar arquivos não-JS diretamente no servidor
23
71
  */
24
72
  function registerLoaders() {
73
+ // Registra resolver de aliases do tsconfig.json
74
+ const aliases = loadTsConfigAliases();
75
+
76
+ if (Object.keys(aliases).length > 0) {
77
+ // Guarda referência ao _resolveFilename atual (pode já ter sido modificado por source-map-support)
78
+ const originalResolveFilename = Module._resolveFilename;
79
+
80
+ Module._resolveFilename = function (request, parent, isMain, options) {
81
+ // Verifica se o request começa com algum alias
82
+ for (const [alias, aliasPath] of Object.entries(aliases)) {
83
+ if (request === alias || request.startsWith(alias + '/')) {
84
+ const relativePath = request.slice(alias.length);
85
+ const resolvedPath = path.join(aliasPath, relativePath);
86
+
87
+ // Tenta resolver com diferentes extensões
88
+ const extensions = ['.tsx', '.ts', '.jsx', '.js'];
89
+
90
+ // Primeiro tenta o caminho exato
91
+ if (fs.existsSync(resolvedPath)) {
92
+ const stats = fs.statSync(resolvedPath);
93
+ if (stats.isFile()) {
94
+ // Retorna o caminho resolvido diretamente
95
+ request = resolvedPath;
96
+ break;
97
+ }
98
+ }
99
+
100
+ // Tenta com extensões
101
+ let resolved = false;
102
+ for (const ext of extensions) {
103
+ const pathWithExt = resolvedPath + ext;
104
+ if (fs.existsSync(pathWithExt)) {
105
+ request = pathWithExt;
106
+ resolved = true;
107
+ break;
108
+ }
109
+ }
110
+
111
+ if (resolved) break;
112
+
113
+ // Tenta como diretório com index
114
+ for (const ext of extensions) {
115
+ const indexPath = path.join(resolvedPath, 'index' + ext);
116
+ if (fs.existsSync(indexPath)) {
117
+ request = indexPath;
118
+ resolved = true;
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (resolved) break;
124
+ }
125
+ }
126
+
127
+ // Chama o resolver original (que pode ser do source-map-support)
128
+ return originalResolveFilename.call(this, request, parent, isMain, options);
129
+ };
130
+ }
131
+
25
132
  // Loader para arquivos Markdown (.md)
26
133
  require.extensions['.md'] = function (module, filename) {
27
134
  const content = fs.readFileSync(filename, 'utf8');