@archznn/xavva 2.6.0 → 2.7.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.
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Parser de código Java
3
+ * Centraliza análise de anotações e estrutura de arquivos .java
4
+ */
5
+
6
+ import type { ApiEndpoint, ApiParam } from "../../types/endpoint";
7
+
8
+ export interface JavaClassInfo {
9
+ className: string;
10
+ package?: string;
11
+ annotations: AnnotationInfo[];
12
+ methods: JavaMethodInfo[];
13
+ }
14
+
15
+ export interface AnnotationInfo {
16
+ name: string;
17
+ value?: string;
18
+ attributes: Record<string, string>;
19
+ }
20
+
21
+ export interface JavaMethodInfo {
22
+ name: string;
23
+ annotations: AnnotationInfo[];
24
+ parameters: JavaParameterInfo[];
25
+ returnType?: string;
26
+ }
27
+
28
+ export interface JavaParameterInfo {
29
+ name: string;
30
+ type: string;
31
+ annotations: AnnotationInfo[];
32
+ }
33
+
34
+ export class JavaParser {
35
+ /**
36
+ * Extrai informações completas de uma classe Java
37
+ */
38
+ static parseClass(content: string, fileName: string): JavaClassInfo {
39
+ const className = fileName.replace(/\.java$/i, "");
40
+
41
+ return {
42
+ className,
43
+ package: this.extractPackage(content),
44
+ annotations: this.extractClassAnnotations(content),
45
+ methods: this.extractMethods(content),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Extrai package declaration
51
+ */
52
+ static extractPackage(content: string): string | undefined {
53
+ const match = content.match(/package\s+([\w.]+)\s*;/);
54
+ return match?.[1];
55
+ }
56
+
57
+ /**
58
+ * Extrai imports
59
+ */
60
+ static extractImports(content: string): string[] {
61
+ const imports: string[] = [];
62
+ const regex = /import\s+([\w.*]+)\s*;/g;
63
+ let match;
64
+ while ((match = regex.exec(content)) !== null) {
65
+ imports.push(match[1]);
66
+ }
67
+ return imports;
68
+ }
69
+
70
+ /**
71
+ * Extrai anotações de classe
72
+ */
73
+ static extractClassAnnotations(content: string): AnnotationInfo[] {
74
+ // Procura anotações antes da declaração da classe
75
+ const classMatch = content.match(/(@[\w\s(,="{}[\]]+)\s+(?:public\s+)?(?:abstract\s+)?class\s+\w+/);
76
+ if (!classMatch) return [];
77
+
78
+ return this.parseAnnotations(classMatch[1]);
79
+ }
80
+
81
+ /**
82
+ * Extrai métodos da classe
83
+ */
84
+ static extractMethods(content: string): JavaMethodInfo[] {
85
+ const methods: JavaMethodInfo[] = [];
86
+
87
+ // Regex melhorado para encontrar métodos
88
+ // Procura por padrões como: @Anotacao public Tipo metodo(params)
89
+ // Também captura métodos sem anotações
90
+ const methodPattern = /((?:@[\w]+(?:\s*\([^)]*\))?\s*)*)\s*(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?([\w<>[\],\s.?]+)\s+(\w+)\s*\(([^)]*)\)\s*(?:throws\s+[\w,\s]+)?\s*[{;]/g;
91
+
92
+ let match;
93
+ while ((match = methodPattern.exec(content)) !== null) {
94
+ const annotationsStr = match[1] || "";
95
+ const returnType = match[2].trim();
96
+ const name = match[3];
97
+ const paramsStr = match[4];
98
+
99
+ // Pula métodos que parecem ser construtores (tipo == nome da classe)
100
+ // ou que não têm tipo de retorno válido
101
+ if (!returnType || returnType === name) continue;
102
+
103
+ methods.push({
104
+ name,
105
+ annotations: this.parseAnnotations(annotationsStr),
106
+ parameters: this.parseParameters(paramsStr),
107
+ returnType,
108
+ });
109
+ }
110
+
111
+ return methods;
112
+ }
113
+
114
+ /**
115
+ * Parse de string de anotações
116
+ */
117
+ static parseAnnotations(annotationsStr: string): AnnotationInfo[] {
118
+ const annotations: AnnotationInfo[] = [];
119
+
120
+ // Regex para anotações: @Nome ou @Nome(atributos)
121
+ const regex = /@(\w+)(?:\s*\(\s*([^)]*)\s*\))?/g;
122
+
123
+ let match;
124
+ while ((match = regex.exec(annotationsStr)) !== null) {
125
+ const name = match[1];
126
+ const attrsStr = match[2] || "";
127
+
128
+ const { value, attributes } = this.parseAnnotationAttributes(attrsStr);
129
+
130
+ annotations.push({ name, value, attributes });
131
+ }
132
+
133
+ return annotations;
134
+ }
135
+
136
+ /**
137
+ * Parse de atributos de anotação
138
+ */
139
+ private static parseAnnotationAttributes(attrsStr: string): { value?: string; attributes: Record<string, string> } {
140
+ const attributes: Record<string, string> = {};
141
+ let value: string | undefined;
142
+
143
+ if (!attrsStr.trim()) {
144
+ return { value, attributes };
145
+ }
146
+
147
+ // Verifica se é valor simples (string literal ou array)
148
+ const simpleValueMatch = attrsStr.match(/^\s*["']([^"']+)["']\s*$/);
149
+ if (simpleValueMatch) {
150
+ value = simpleValueMatch[1];
151
+ return { value, attributes };
152
+ }
153
+
154
+ // Parse de atributos chave=valor
155
+ // Pega tudo entre aspas após um sinal de igual
156
+ const attrRegex = /(\w+)\s*=\s*["']([^"']*?)["']/g;
157
+ let attrMatch;
158
+ while ((attrMatch = attrRegex.exec(attrsStr)) !== null) {
159
+ const key = attrMatch[1];
160
+ const val = attrMatch[2];
161
+
162
+ if (key === "value") {
163
+ value = val;
164
+ } else {
165
+ attributes[key] = val;
166
+ }
167
+ }
168
+
169
+ // Se não encontrou atributos com regex, tenta extrair valor como string simples
170
+ if (Object.keys(attributes).length === 0 && !value) {
171
+ const simpleMatch = attrsStr.match(/["']([^"']+)["']/);
172
+ if (simpleMatch) {
173
+ value = simpleMatch[1];
174
+ }
175
+ }
176
+
177
+ return { value, attributes };
178
+ }
179
+
180
+ /**
181
+ * Parse de parâmetros de método
182
+ */
183
+ static parseParameters(paramsStr: string): JavaParameterInfo[] {
184
+ const parameters: JavaParameterInfo[] = [];
185
+
186
+ if (!paramsStr.trim()) {
187
+ return parameters;
188
+ }
189
+
190
+ // Divide por vírgula, mas cuida com generics
191
+ const params = this.splitParams(paramsStr);
192
+
193
+ for (const param of params) {
194
+ const trimmed = param.trim();
195
+ if (!trimmed) continue;
196
+
197
+ // Extrai anotações do parâmetro
198
+ const annotations: AnnotationInfo[] = [];
199
+ let paramWithoutAnnotations = trimmed;
200
+
201
+ const annoRegex = /@(\w+)\s*\(\s*["']([^"']+)["']\s*\)/g;
202
+ let annoMatch;
203
+ while ((annoMatch = annoRegex.exec(trimmed)) !== null) {
204
+ annotations.push({
205
+ name: annoMatch[1],
206
+ value: annoMatch[2],
207
+ attributes: {},
208
+ });
209
+ paramWithoutAnnotations = paramWithoutAnnotations.replace(annoMatch[0], "").trim();
210
+ }
211
+
212
+ // Tipo e nome
213
+ const parts = paramWithoutAnnotations.split(/\s+/);
214
+ if (parts.length >= 2) {
215
+ parameters.push({
216
+ type: parts.slice(0, -1).join(" "),
217
+ name: parts[parts.length - 1],
218
+ annotations,
219
+ });
220
+ }
221
+ }
222
+
223
+ return parameters;
224
+ }
225
+
226
+ /**
227
+ * Divide string de parâmetros respeitando generics
228
+ */
229
+ private static splitParams(paramsStr: string): string[] {
230
+ const result: string[] = [];
231
+ let current = "";
232
+ let depth = 0;
233
+
234
+ for (const char of paramsStr) {
235
+ if (char === "<") {
236
+ depth++;
237
+ current += char;
238
+ } else if (char === ">") {
239
+ depth--;
240
+ current += char;
241
+ } else if (char === "," && depth === 0) {
242
+ result.push(current);
243
+ current = "";
244
+ } else {
245
+ current += char;
246
+ }
247
+ }
248
+
249
+ if (current.trim()) {
250
+ result.push(current);
251
+ }
252
+
253
+ return result;
254
+ }
255
+
256
+ /**
257
+ * Extrai endpoints REST de um arquivo Java
258
+ * Suporta Spring MVC, JAX-RS, Jersey
259
+ */
260
+ static extractEndpoints(content: string, fileName: string, contextPath: string = ""): ApiEndpoint[] {
261
+ const classInfo = this.parseClass(content, fileName);
262
+ const endpoints: ApiEndpoint[] = [];
263
+
264
+ // Encontra path base da classe
265
+ const basePath = this.extractBasePath(classInfo.annotations);
266
+
267
+ for (const method of classInfo.methods) {
268
+ const methodPath = this.extractMethodPath(method.annotations);
269
+ const httpMethod = this.inferHttpMethod(method.annotations);
270
+
271
+ if (!methodPath && httpMethod === "ALL") continue;
272
+
273
+ const fullPath = this.combinePaths(contextPath, basePath, methodPath);
274
+
275
+ endpoints.push({
276
+ method: httpMethod,
277
+ path: methodPath || "/",
278
+ fullPath,
279
+ className: classInfo.className,
280
+ methodName: method.name,
281
+ parameters: this.convertParams(method.parameters),
282
+ });
283
+ }
284
+
285
+ return endpoints;
286
+ }
287
+
288
+ /**
289
+ * Extrai path base de anotações de classe
290
+ */
291
+ private static extractBasePath(annotations: AnnotationInfo[]): string {
292
+ // Spring: @RequestMapping("/path") ou @Path("/path") (JAX-RS)
293
+ const mapping = annotations.find(a =>
294
+ a.name === "RequestMapping" || a.name === "Path"
295
+ );
296
+ return mapping?.value || "";
297
+ }
298
+
299
+ /**
300
+ * Extrai path de método de anotações
301
+ */
302
+ private static extractMethodPath(annotations: AnnotationInfo[]): string {
303
+ // Procura qualquer anotação que tenha valor de path
304
+ const pathAnnotations = [
305
+ "RequestMapping", "GetMapping", "PostMapping",
306
+ "PutMapping", "DeleteMapping", "PatchMapping",
307
+ "Path"
308
+ ];
309
+
310
+ const mapping = annotations.find(a => pathAnnotations.includes(a.name));
311
+ return mapping?.value || "";
312
+ }
313
+
314
+ /**
315
+ * Infere método HTTP de anotações
316
+ */
317
+ private static inferHttpMethod(annotations: AnnotationInfo[]): ApiEndpoint["method"] {
318
+ const annoNames = annotations.map(a => a.name);
319
+
320
+ if (annoNames.includes("GetMapping")) return "GET";
321
+ if (annoNames.includes("PostMapping")) return "POST";
322
+ if (annoNames.includes("PutMapping")) return "PUT";
323
+ if (annoNames.includes("DeleteMapping")) return "DELETE";
324
+ if (annoNames.includes("PatchMapping")) return "PATCH";
325
+
326
+ // JAX-RS
327
+ if (annoNames.includes("GET")) return "GET";
328
+ if (annoNames.includes("POST")) return "POST";
329
+ if (annoNames.includes("PUT")) return "PUT";
330
+ if (annoNames.includes("DELETE")) return "DELETE";
331
+ if (annoNames.includes("PATCH")) return "PATCH";
332
+
333
+ // RequestMapping genérico
334
+ if (annoNames.includes("RequestMapping")) {
335
+ const mapping = annotations.find(a => a.name === "RequestMapping");
336
+ const methodAttr = mapping?.attributes?.method;
337
+ if (methodAttr) {
338
+ const method = methodAttr.replace(/RequestMethod\./g, "").toUpperCase();
339
+ if (["GET", "POST", "PUT", "DELETE", "PATCH"].includes(method)) {
340
+ return method as ApiEndpoint["method"];
341
+ }
342
+ }
343
+ return "ALL";
344
+ }
345
+
346
+ return "ALL";
347
+ }
348
+
349
+ /**
350
+ * Converte parâmetros Java para ApiParam
351
+ */
352
+ private static convertParams(params: JavaParameterInfo[]): ApiParam[] {
353
+ return params.map(p => {
354
+ const source = this.inferParamSource(p.annotations);
355
+ const required = !p.annotations.some(a =>
356
+ a.name === "RequestParam" && a.attributes?.required === "false"
357
+ );
358
+
359
+ return {
360
+ name: p.name,
361
+ type: p.type,
362
+ source,
363
+ required,
364
+ };
365
+ });
366
+ }
367
+
368
+ /**
369
+ * Infere fonte do parâmetro (PATH, QUERY, BODY, HEADER)
370
+ */
371
+ private static inferParamSource(annotations: AnnotationInfo[]): ApiParam["source"] {
372
+ const annoNames = annotations.map(a => a.name);
373
+
374
+ if (annoNames.includes("PathVariable") || annoNames.includes("PathParam")) {
375
+ return "PATH";
376
+ }
377
+ if (annoNames.includes("RequestParam") || annoNames.includes("QueryParam")) {
378
+ return "QUERY";
379
+ }
380
+ if (annoNames.includes("RequestBody")) {
381
+ return "BODY";
382
+ }
383
+ if (annoNames.includes("RequestHeader") || annoNames.includes("HeaderParam")) {
384
+ return "HEADER";
385
+ }
386
+
387
+ return "QUERY"; // Default
388
+ }
389
+
390
+ /**
391
+ * Combina múltiplos paths
392
+ */
393
+ private static combinePaths(...parts: string[]): string {
394
+ return parts
395
+ .map(p => p.trim())
396
+ .map(p => (p.startsWith("/") ? p : "/" + p))
397
+ .map(p => (p.endsWith("/") && p.length > 1 ? p.slice(0, -1) : p))
398
+ .filter(p => p && p !== "/")
399
+ .join("") || "/";
400
+ }
401
+ }
402
+
403
+ // Exporta funções standalone
404
+ export const {
405
+ parseClass,
406
+ extractPackage,
407
+ extractImports,
408
+ extractClassAnnotations,
409
+ extractMethods,
410
+ parseAnnotations,
411
+ parseParameters,
412
+ extractEndpoints,
413
+ } = JavaParser;
@@ -243,8 +243,8 @@ export function getOpenBrowserCommand(): string {
243
243
  export function getOpenBrowserArgs(url: string): string[] {
244
244
  const cmd = getOpenBrowserCommand();
245
245
  if (isWindows()) {
246
- // Windows: start "" "url" (o "" é necessário para títulos de janela)
247
- return [cmd, "", url];
246
+ // Windows: usa cmd /c start "" "url" (start é comando interno do cmd)
247
+ return ["cmd", "/c", "start", "", url];
248
248
  }
249
249
  return [cmd, url];
250
250
  }
@@ -1,117 +0,0 @@
1
- import { watch } from "fs";
2
- import { Logger } from "../utils/ui";
3
- import { DeployCommand } from "../commands/DeployCommand";
4
- import { WATCHER_DEBOUNCE_MS, WATCHER_COOLING_MS } from "../utils/constants";
5
- import type { AppConfig } from "../types/config";
6
-
7
- export class WatcherService {
8
- private isDeploying = false;
9
- private pendingFullBuild = false;
10
- private coolingFiles = new Set<string>();
11
- private debounceTimer?: Timer;
12
-
13
- // Rastreamento de arquivos modificados para build incremental inteligente
14
- private modifiedFiles = new Set<string>();
15
- private pendingFiles = new Set<string>(); // Arquivos modificados durante compilação
16
- private hasPendingChanges = false;
17
-
18
- constructor(private config: AppConfig, private deployCmd: DeployCommand) {}
19
-
20
- public async start() {
21
- await this.run(false);
22
-
23
- watch(process.cwd(), { recursive: true }, async (event, filename) => {
24
- if (!filename) return;
25
-
26
- if (this.coolingFiles.has(filename)) return;
27
- this.coolingFiles.add(filename);
28
- setTimeout(() => this.coolingFiles.delete(filename), WATCHER_COOLING_MS);
29
-
30
- if (this.isIgnored(filename)) return;
31
-
32
- const isBuildConfig = filename === "pom.xml" || filename === "build.gradle" || filename === "build.gradle.kts";
33
- const isJava = filename.endsWith(".java") || isBuildConfig;
34
- const isResource = this.isResourceFile(filename);
35
-
36
- if (isBuildConfig) {
37
- Logger.watcher(`Build configuration changed: ${filename}`, 'warn');
38
- const { BuildCacheService } = await import("./BuildCacheService");
39
- new BuildCacheService().clearCache();
40
- this.pendingFullBuild = true;
41
- }
42
-
43
- if (isResource && !isJava) {
44
- await this.deployCmd.syncResource(this.config, filename);
45
- return;
46
- }
47
-
48
- if (!isJava) return;
49
-
50
- Logger.watcher(filename, 'watch');
51
-
52
- // Se estiver compilando, acumula na fila de pendentes
53
- if (this.isDeploying) {
54
- this.pendingFiles.add(filename);
55
- this.hasPendingChanges = true;
56
- return;
57
- }
58
-
59
- // Acumula arquivos modificados para o próximo build
60
- this.modifiedFiles.add(filename);
61
-
62
- clearTimeout(this.debounceTimer);
63
-
64
- this.debounceTimer = setTimeout(() => {
65
- const filesToCompile = [...this.modifiedFiles];
66
- this.modifiedFiles.clear();
67
- this.run(this.pendingFullBuild ? false : true, filesToCompile);
68
- this.pendingFullBuild = false;
69
- }, WATCHER_DEBOUNCE_MS);
70
- });
71
- }
72
-
73
- private async run(incremental = false, changedFiles?: string[]) {
74
- if (this.isDeploying) return;
75
- this.isDeploying = true;
76
-
77
- try {
78
- // Passa os arquivos específicos que foram modificados
79
- await this.deployCmd.execute(this.config, {
80
- watch: true,
81
- incremental,
82
- changedFiles
83
- });
84
- } catch (e) {
85
- // Error handled by command
86
- } finally {
87
- this.isDeploying = false;
88
-
89
- // Se houve mudanças durante a compilação, processa imediatamente
90
- if (this.hasPendingChanges) {
91
- const pending = [...this.pendingFiles];
92
- this.pendingFiles.clear();
93
- this.hasPendingChanges = false;
94
-
95
- Logger.watcher(`Processing ${pending.length} pending change(s)...`, 'warn');
96
-
97
- // Pequeno delay para garantir que os arquivos foram salvos completamente
98
- setTimeout(() => {
99
- this.run(true, pending);
100
- }, 100);
101
- }
102
- }
103
- }
104
-
105
- private isIgnored(filename: string): boolean {
106
- return filename.includes("target") ||
107
- filename.includes("build") ||
108
- filename.includes("node_modules") ||
109
- filename.split(/[/\\]/).some(part => part.startsWith("."));
110
- }
111
-
112
- private isResourceFile(filename: string): boolean {
113
- return filename.endsWith(".jsp") || filename.endsWith(".html") ||
114
- filename.endsWith(".css") || filename.endsWith(".js") ||
115
- filename.endsWith(".xml") || filename.endsWith(".properties");
116
- }
117
- }