vatts 1.2.0-alpha.1 → 1.2.0-test.0
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.
- package/dist/adapters/express.js +5 -1
- package/dist/adapters/factory.js +58 -21
- package/dist/adapters/fastify.js +5 -1
- package/dist/adapters/native.js +5 -1
- package/dist/api/console.js +25 -17
- package/dist/api/framework.js +22 -15
- package/dist/api/http.js +7 -2
- package/dist/builder.js +19 -10
- package/dist/client/clientRouter.js +6 -2
- package/dist/client/rpc.js +7 -4
- package/dist/env/env.js +18 -11
- package/dist/global/global.d.ts +177 -122
- package/dist/helpers.js +108 -67
- package/dist/hotReload.d.ts +6 -0
- package/dist/hotReload.js +179 -31
- package/dist/index.js +159 -115
- package/dist/loaders.js +29 -13
- package/dist/react/BuildingPage.d.ts +2 -1
- package/dist/react/BuildingPage.js +47 -4
- package/dist/react/DefaultNotFound.d.ts +2 -1
- package/dist/react/DefaultNotFound.js +92 -17
- package/dist/react/DevIndicator.js +66 -23
- package/dist/react/ErrorModal.js +91 -40
- package/dist/react/Link.d.ts +2 -2
- package/dist/react/Link.js +27 -5
- package/dist/react/client.js +16 -5
- package/dist/react/entry.client.js +70 -30
- package/dist/react/image/Image.js +8 -3
- package/dist/react/renderer-react.js +53 -25
- package/dist/renderer.d.ts +4 -0
- package/dist/renderer.js +13 -5
- package/dist/router.js +82 -63
- package/dist/rpc/annotations.js +7 -3
- package/dist/rpc/server.js +21 -15
- package/dist/rpc/types.js +4 -1
- package/dist/types/framework.js +2 -1
- package/dist/types.js +2 -1
- package/dist/vue/App.vue +34 -37
- package/dist/vue/BuildingPage.vue +118 -102
- package/dist/vue/ErrorModal.vue +19 -37
- package/dist/vue/Link.vue +8 -7
- package/dist/vue/client.js +16 -6
- package/dist/vue/entry.client.js +8 -3
- package/dist/vue/image/Image.vue +25 -19
- package/dist/vue/renderer.vue.js +80 -26
- package/package.json +25 -12
- package/dist/global/global.js +0 -17
package/dist/hotReload.js
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.HotReloadManager = void 0;
|
|
1
40
|
/*
|
|
2
41
|
* This file is part of the Vatts.js Project.
|
|
3
42
|
* Copyright (c) 2026 itsmuzin
|
|
@@ -14,16 +53,17 @@
|
|
|
14
53
|
* See the License for the specific language governing permissions and
|
|
15
54
|
* limitations under the License.
|
|
16
55
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
56
|
+
const ws_1 = require("ws");
|
|
57
|
+
const chokidar = __importStar(require("chokidar"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const fs = __importStar(require("fs"));
|
|
60
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
61
|
+
const router_1 = require("./router");
|
|
62
|
+
const console_1 = __importStar(require("./api/console"));
|
|
23
63
|
// Chaves para persistência global para sobreviver a reloads do backend
|
|
24
64
|
const GLOBAL_ERROR_KEY = '__VATTS_LAST_BUILD_ERROR__';
|
|
25
65
|
const GLOBAL_ACTIVE_MANAGER_KEY = '__VATTS_ACTIVE_HOT_RELOAD_MANAGER__';
|
|
26
|
-
|
|
66
|
+
class HotReloadManager {
|
|
27
67
|
wss = null;
|
|
28
68
|
watchers = [];
|
|
29
69
|
projectDir;
|
|
@@ -67,7 +107,7 @@ export class HotReloadManager {
|
|
|
67
107
|
return;
|
|
68
108
|
}
|
|
69
109
|
if (!this.wss) {
|
|
70
|
-
this.wss = new WebSocketServer({
|
|
110
|
+
this.wss = new ws_1.WebSocketServer({
|
|
71
111
|
noServer: true,
|
|
72
112
|
perMessageDeflate: false,
|
|
73
113
|
maxPayload: 1024 * 1024
|
|
@@ -88,7 +128,7 @@ export class HotReloadManager {
|
|
|
88
128
|
}
|
|
89
129
|
const pingTimer = setInterval(() => {
|
|
90
130
|
const client = this.clients.get(ws);
|
|
91
|
-
if (client && ws.readyState === WebSocket.OPEN) {
|
|
131
|
+
if (client && ws.readyState === ws_1.WebSocket.OPEN) {
|
|
92
132
|
if (Date.now() - client.lastPong > 60000) {
|
|
93
133
|
ws.terminate();
|
|
94
134
|
return;
|
|
@@ -104,7 +144,7 @@ export class HotReloadManager {
|
|
|
104
144
|
this.clients.set(ws, clientConnection);
|
|
105
145
|
// Ao conectar, envia o status atual combinado
|
|
106
146
|
setTimeout(() => {
|
|
107
|
-
if (ws.readyState !== WebSocket.OPEN)
|
|
147
|
+
if (ws.readyState !== ws_1.WebSocket.OPEN)
|
|
108
148
|
return;
|
|
109
149
|
this.broadcastCurrentState(ws);
|
|
110
150
|
}, 0);
|
|
@@ -129,7 +169,7 @@ export class HotReloadManager {
|
|
|
129
169
|
this.cleanupClient(ws);
|
|
130
170
|
});
|
|
131
171
|
ws.on('error', (error) => {
|
|
132
|
-
|
|
172
|
+
console_1.default.logWithout(console_1.Levels.ERROR, console_1.Colors.BgRed, `WebSocket error: ${error.message}`);
|
|
133
173
|
this.cleanupClient(ws);
|
|
134
174
|
});
|
|
135
175
|
});
|
|
@@ -182,8 +222,8 @@ export class HotReloadManager {
|
|
|
182
222
|
watcher.on('change', debouncedChange);
|
|
183
223
|
watcher.on('add', debouncedChange);
|
|
184
224
|
watcher.on('unlink', (filePath) => {
|
|
185
|
-
|
|
186
|
-
clearFileCache(filePath);
|
|
225
|
+
console_1.default.info(`File removed: ${path.basename(filePath)}`);
|
|
226
|
+
(0, router_1.clearFileCache)(filePath);
|
|
187
227
|
this.clearBackendCache(filePath);
|
|
188
228
|
// Unlink também precisa de try-catch se for chamar callbacks
|
|
189
229
|
try {
|
|
@@ -233,7 +273,7 @@ export class HotReloadManager {
|
|
|
233
273
|
errorData.message.includes('TS2304') ||
|
|
234
274
|
errorData.message.includes('TS1005') ||
|
|
235
275
|
errorData.message.includes('TS17002'));
|
|
236
|
-
|
|
276
|
+
console_1.default.error("Captured Backend Error:", errorData.message);
|
|
237
277
|
this.buildState = {
|
|
238
278
|
...currentState,
|
|
239
279
|
backend: errorData,
|
|
@@ -243,20 +283,22 @@ export class HotReloadManager {
|
|
|
243
283
|
this.notifyStatusChange();
|
|
244
284
|
}
|
|
245
285
|
async handleAnySrcChange(filePath) {
|
|
246
|
-
const dm =
|
|
286
|
+
const dm = console_1.default.dynamicLine(`File change detected ${path.basename(filePath)}, processing...`);
|
|
247
287
|
let hasBackendError = false;
|
|
248
288
|
const isFrontendFile = filePath.includes(path.join('src', 'web', 'routes')) ||
|
|
249
289
|
filePath.includes(path.join('src', 'web', 'components')) ||
|
|
250
290
|
filePath.includes('layout.tsx') ||
|
|
251
291
|
filePath.includes('not-found.tsx') ||
|
|
252
|
-
filePath.endsWith('.tsx')
|
|
292
|
+
filePath.endsWith('.tsx') ||
|
|
293
|
+
filePath.endsWith(".ts") ||
|
|
294
|
+
filePath.endsWith(".vue");
|
|
253
295
|
const isBackendFile = filePath.includes(path.join('src', 'backend')) && !isFrontendFile;
|
|
254
|
-
clearFileCache(filePath);
|
|
296
|
+
(0, router_1.clearFileCache)(filePath);
|
|
255
297
|
this.clearBackendCache(filePath);
|
|
256
298
|
// Tenta executar os callbacks e captura erros do Backend (Router/SSR)
|
|
257
299
|
try {
|
|
258
300
|
if (isFrontendFile) {
|
|
259
|
-
|
|
301
|
+
console_1.default.logWithout(console_1.Levels.INFO, undefined, `Frontend change detected: ${path.basename(filePath)}`);
|
|
260
302
|
// CRÍTICO: Executa e captura erro
|
|
261
303
|
try {
|
|
262
304
|
this.frontendChangeCallback?.();
|
|
@@ -275,7 +317,7 @@ export class HotReloadManager {
|
|
|
275
317
|
this.notifyClients('frontend-reload', { file: filePath, event: 'change' });
|
|
276
318
|
}
|
|
277
319
|
else if (isBackendFile) {
|
|
278
|
-
|
|
320
|
+
console_1.default.logWithout(console_1.Levels.INFO, undefined, `Backend change detected: ${path.basename(filePath)}. Reloading backend...`);
|
|
279
321
|
try {
|
|
280
322
|
this.backendApiChangeCallback?.();
|
|
281
323
|
// Se passou aqui, limpa erro de backend anterior
|
|
@@ -323,7 +365,7 @@ export class HotReloadManager {
|
|
|
323
365
|
}
|
|
324
366
|
catch (error) {
|
|
325
367
|
// @ts-ignore
|
|
326
|
-
|
|
368
|
+
console_1.default.logWithout(console_1.Levels.ERROR, `Error in custom listener: ${error.message}`);
|
|
327
369
|
}
|
|
328
370
|
}
|
|
329
371
|
}
|
|
@@ -351,7 +393,7 @@ export class HotReloadManager {
|
|
|
351
393
|
const message = JSON.stringify({ type, data, timestamp: Date.now() });
|
|
352
394
|
const deadClients = [];
|
|
353
395
|
this.clients.forEach((client, ws) => {
|
|
354
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
396
|
+
if (ws.readyState === ws_1.WebSocket.OPEN) {
|
|
355
397
|
try {
|
|
356
398
|
ws.send(message);
|
|
357
399
|
}
|
|
@@ -589,7 +631,7 @@ export class HotReloadManager {
|
|
|
589
631
|
onFrontendChange(callback) { this.frontendChangeCallback = callback; }
|
|
590
632
|
setHotReloadListener(listener) {
|
|
591
633
|
this.customHotReloadListener = listener;
|
|
592
|
-
|
|
634
|
+
console_1.default.info('Hot reload custom listener registered');
|
|
593
635
|
}
|
|
594
636
|
removeHotReloadListener() { this.customHotReloadListener = null; }
|
|
595
637
|
// DEBUG: stack traces (rate-limited) para descobrir quem dispara onBuildComplete
|
|
@@ -622,7 +664,7 @@ export class HotReloadManager {
|
|
|
622
664
|
const currentState = this.buildState;
|
|
623
665
|
const buildId = typeof error?.buildId === 'number' ? error.buildId : undefined;
|
|
624
666
|
if (success) {
|
|
625
|
-
// Não confia no bundler. Antes de ficar "verde", roda typecheck real.
|
|
667
|
+
// Não confia no bundler. Antes de ficar "verde", roda typecheck real (React + Vue).
|
|
626
668
|
const tc = this.typecheckFrontend();
|
|
627
669
|
if (!tc.ok) {
|
|
628
670
|
// Se já existe um erro de bundler (esbuild/rollup) mais específico, preserva ele.
|
|
@@ -664,7 +706,7 @@ export class HotReloadManager {
|
|
|
664
706
|
this.watchers = [];
|
|
665
707
|
this.clients.forEach((client, ws) => {
|
|
666
708
|
clearInterval(client.pingTimer);
|
|
667
|
-
if (ws.readyState === WebSocket.OPEN)
|
|
709
|
+
if (ws.readyState === ws_1.WebSocket.OPEN)
|
|
668
710
|
ws.close();
|
|
669
711
|
});
|
|
670
712
|
this.clients.clear();
|
|
@@ -675,40 +717,145 @@ export class HotReloadManager {
|
|
|
675
717
|
}
|
|
676
718
|
lastTypecheckAt = 0;
|
|
677
719
|
lastTypecheckResult = null;
|
|
720
|
+
/**
|
|
721
|
+
* Verifica erros específicos de arquivos Vue usando @vue/compiler-sfc.
|
|
722
|
+
* Isso permite capturar erros de sintaxe (como falta de ponto e vírgula, tags mal fechadas)
|
|
723
|
+
* que o compilador padrão pode emitir mas que precisam ser formatados para o cliente.
|
|
724
|
+
*/
|
|
725
|
+
checkVueFiles() {
|
|
726
|
+
try {
|
|
727
|
+
// 1. Tenta carregar o compilador do Vue dinamicamente
|
|
728
|
+
let compiler;
|
|
729
|
+
try {
|
|
730
|
+
compiler = require('vue/compiler-sfc');
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
try {
|
|
734
|
+
compiler = require('@vue/compiler-sfc');
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// Não é um projeto Vue ou dependência faltando, apenas ignora
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// 2. Scan síncrono para encontrar arquivos .vue em src/web
|
|
742
|
+
// Em projetos gigantes isso deve ser otimizado, mas para dev server é ok.
|
|
743
|
+
const findVueFiles = (dir) => {
|
|
744
|
+
let results = [];
|
|
745
|
+
if (!fs.existsSync(dir))
|
|
746
|
+
return results;
|
|
747
|
+
const list = fs.readdirSync(dir);
|
|
748
|
+
list.forEach(file => {
|
|
749
|
+
const filePath = path.join(dir, file);
|
|
750
|
+
const stat = fs.statSync(filePath);
|
|
751
|
+
if (stat && stat.isDirectory()) {
|
|
752
|
+
if (file !== 'node_modules' && file !== '.git' && file !== 'dist') {
|
|
753
|
+
results = results.concat(findVueFiles(filePath));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else if (file.endsWith('.vue')) {
|
|
757
|
+
results.push(filePath);
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
return results;
|
|
761
|
+
};
|
|
762
|
+
const webSrc = path.join(this.projectDir, 'src');
|
|
763
|
+
const vueFiles = findVueFiles(webSrc);
|
|
764
|
+
if (vueFiles.length === 0)
|
|
765
|
+
return { ok: true };
|
|
766
|
+
// 3. Analisa cada arquivo Vue
|
|
767
|
+
for (const file of vueFiles) {
|
|
768
|
+
try {
|
|
769
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
770
|
+
const parsed = compiler.parse(content, {
|
|
771
|
+
filename: file,
|
|
772
|
+
sourceMap: false
|
|
773
|
+
});
|
|
774
|
+
// Verifica erros de compilação do template/script
|
|
775
|
+
if (parsed.errors && parsed.errors.length > 0) {
|
|
776
|
+
const firstError = parsed.errors[0];
|
|
777
|
+
const loc = firstError.loc ? {
|
|
778
|
+
file: file,
|
|
779
|
+
line: firstError.loc.start.line,
|
|
780
|
+
column: firstError.loc.start.column
|
|
781
|
+
} : undefined;
|
|
782
|
+
// Stack simulada para o overlay exibir bonito
|
|
783
|
+
const syntheticStack = `SyntaxError: ${firstError.message}\n at ${file}:${loc?.line}:${loc?.column}`;
|
|
784
|
+
return {
|
|
785
|
+
ok: false,
|
|
786
|
+
error: {
|
|
787
|
+
message: `Vue Error: ${firstError.message}`,
|
|
788
|
+
type: 'VueCompilerError',
|
|
789
|
+
loc,
|
|
790
|
+
stack: syntheticStack,
|
|
791
|
+
ts: Date.now()
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch (readError) {
|
|
797
|
+
// Ignora erro de leitura de arquivo individual
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
catch (e) {
|
|
802
|
+
// Ignora falha geral no checker do Vue
|
|
803
|
+
}
|
|
804
|
+
return { ok: true };
|
|
805
|
+
}
|
|
678
806
|
typecheckFrontend() {
|
|
679
807
|
try {
|
|
680
808
|
const now = Date.now();
|
|
681
809
|
if (this.lastTypecheckResult && now - this.lastTypecheckAt < 750)
|
|
682
810
|
return this.lastTypecheckResult;
|
|
811
|
+
// --- 1. Vue Check (New) ---
|
|
812
|
+
// Verifica arquivos Vue primeiro, pois o TS compiler padrão pode ignorá-los
|
|
813
|
+
// ou dar erros confusos se não estiver configurado com plugins.
|
|
814
|
+
const vueResult = this.checkVueFiles();
|
|
815
|
+
if (vueResult && !vueResult.ok) {
|
|
816
|
+
this.lastTypecheckAt = now;
|
|
817
|
+
this.lastTypecheckResult = vueResult;
|
|
818
|
+
return vueResult;
|
|
819
|
+
}
|
|
820
|
+
// --- 2. TypeScript Check (Existing) ---
|
|
683
821
|
const projectDir = this.projectDir;
|
|
684
|
-
const configPath =
|
|
822
|
+
const configPath = typescript_1.default.findConfigFile(projectDir, typescript_1.default.sys.fileExists, 'tsconfig.json');
|
|
685
823
|
if (!configPath) {
|
|
686
824
|
this.lastTypecheckAt = now;
|
|
687
825
|
this.lastTypecheckResult = { ok: true };
|
|
688
826
|
return this.lastTypecheckResult;
|
|
689
827
|
}
|
|
690
|
-
const configFile =
|
|
828
|
+
const configFile = typescript_1.default.readConfigFile(configPath, typescript_1.default.sys.readFile);
|
|
691
829
|
if (configFile.error) {
|
|
692
|
-
const msg =
|
|
830
|
+
const msg = typescript_1.default.flattenDiagnosticMessageText(configFile.error.messageText, '\n');
|
|
693
831
|
const err = { message: `tsconfig read error: ${msg}`, type: 'TypeScriptConfigError', ts: Date.now() };
|
|
694
832
|
this.lastTypecheckAt = now;
|
|
695
833
|
this.lastTypecheckResult = { ok: false, error: err };
|
|
696
834
|
return this.lastTypecheckResult;
|
|
697
835
|
}
|
|
698
|
-
const parsed =
|
|
836
|
+
const parsed = typescript_1.default.parseJsonConfigFileContent(configFile.config, typescript_1.default.sys, path.dirname(configPath));
|
|
699
837
|
const options = { ...parsed.options, noEmit: true };
|
|
700
838
|
const rootNames = parsed.fileNames;
|
|
701
839
|
const webRoot = path.resolve(projectDir, 'src', 'web') + path.sep;
|
|
840
|
+
// Filtra apenas arquivos dentro de src/web para não checar backend aqui
|
|
702
841
|
const filteredRoots = rootNames.filter(f => f.startsWith(webRoot));
|
|
703
|
-
|
|
704
|
-
|
|
842
|
+
// Se for um projeto Vue puro sem .ts/.tsx explícito na config, o createProgram pode ficar vazio,
|
|
843
|
+
// então usamos os rootNames originais se o filtro zerar tudo.
|
|
844
|
+
const filesToCheck = filteredRoots.length ? filteredRoots : rootNames;
|
|
845
|
+
if (filesToCheck.length === 0) {
|
|
846
|
+
this.lastTypecheckAt = now;
|
|
847
|
+
this.lastTypecheckResult = { ok: true };
|
|
848
|
+
return this.lastTypecheckResult;
|
|
849
|
+
}
|
|
850
|
+
const program = typescript_1.default.createProgram(filesToCheck, options);
|
|
851
|
+
const diagnostics = typescript_1.default.getPreEmitDiagnostics(program);
|
|
705
852
|
if (!diagnostics.length) {
|
|
706
853
|
this.lastTypecheckAt = now;
|
|
707
854
|
this.lastTypecheckResult = { ok: true };
|
|
708
855
|
return this.lastTypecheckResult;
|
|
709
856
|
}
|
|
710
857
|
const first = diagnostics[0];
|
|
711
|
-
const message =
|
|
858
|
+
const message = typescript_1.default.flattenDiagnosticMessageText(first.messageText, '\n');
|
|
712
859
|
const code = typeof first.code === 'number' ? `TS${first.code}` : undefined;
|
|
713
860
|
const loc = first.file && typeof first.start === 'number'
|
|
714
861
|
? (() => {
|
|
@@ -743,3 +890,4 @@ export class HotReloadManager {
|
|
|
743
890
|
}
|
|
744
891
|
}
|
|
745
892
|
}
|
|
893
|
+
exports.HotReloadManager = HotReloadManager;
|