@archznn/xavva 1.6.5 → 1.8.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.
@@ -1,14 +1,30 @@
1
- import { readdirSync, copyFileSync, existsSync, statSync, mkdirSync } from "fs";
1
+ import { readdirSync, existsSync, statSync, mkdirSync, promises as fs } from "fs";
2
2
  import path from "path";
3
3
  import type { ProjectConfig, TomcatConfig } from "../types/config";
4
4
  import { Logger } from "../utils/ui";
5
+ import { BuildCacheService } from "./BuildCacheService";
6
+ import { ProjectService } from "./ProjectService";
5
7
 
6
8
  export class BuildService {
7
- private inferredAppName: string | null = null;
8
-
9
- constructor(private projectConfig: ProjectConfig, private tomcatConfig: TomcatConfig) { }
9
+ constructor(
10
+ private projectConfig: ProjectConfig,
11
+ private tomcatConfig: TomcatConfig,
12
+ private projectService: ProjectService,
13
+ private cache: BuildCacheService
14
+ ) { }
10
15
 
11
16
  async runBuild(incremental = false) {
17
+ if (this.projectConfig.clean) {
18
+ this.cache.clearCache();
19
+ }
20
+
21
+ if (!incremental && !this.projectConfig.skipBuild) {
22
+ if (!this.projectConfig.clean && !this.cache.shouldRebuild(this.projectConfig.buildTool)) {
23
+ Logger.success("Build cache hit! Skipping full build.");
24
+ return;
25
+ }
26
+ }
27
+
12
28
  const command = [];
13
29
 
14
30
  if (this.projectConfig.buildTool === 'maven') {
@@ -16,18 +32,22 @@ export class BuildService {
16
32
  if (incremental) {
17
33
  command.push("compile");
18
34
  } else {
19
- command.push("clean", "package");
35
+ if (this.projectConfig.clean) command.push("clean");
36
+ command.push("compile", "war:exploded");
37
+ command.push("-T", "1C");
20
38
  }
21
- command.push("-DskipTests");
39
+ command.push("-Dmaven.test.skip=true", "-Dmaven.javadoc.skip=true");
22
40
  if (this.projectConfig.profile) command.push(`-P${this.projectConfig.profile}`);
23
41
  } else {
24
42
  command.push("gradle");
25
43
  if (incremental) {
26
44
  command.push("classes");
27
45
  } else {
28
- command.push("clean", "build");
46
+ if (this.projectConfig.clean) command.push("clean");
47
+ command.push("war");
48
+ command.push("--parallel", "--build-cache");
29
49
  }
30
- command.push("-x", "test");
50
+ command.push("-x", "test", "-x", "javadoc");
31
51
  if (this.projectConfig.profile) command.push(`-Pprofile=${this.projectConfig.profile}`);
32
52
  }
33
53
 
@@ -40,8 +60,8 @@ export class BuildService {
40
60
 
41
61
  if (this.projectConfig.verbose) {
42
62
  await Promise.all([
43
- this.processBuildLogs(proc.stdout, false),
44
- this.processBuildLogs(proc.stderr, false)
63
+ this.processBuildLogs(proc.stdout as ReadableStream, false),
64
+ this.processBuildLogs(proc.stderr as ReadableStream, false)
45
65
  ]);
46
66
  }
47
67
 
@@ -51,11 +71,15 @@ export class BuildService {
51
71
  if (proc.exitCode !== 0) {
52
72
  if (!this.projectConfig.verbose) {
53
73
  const err = await new Response(proc.stderr).text();
54
- console.log(err);
74
+ Logger.log(err);
55
75
  }
56
76
  Logger.error(`${this.projectConfig.buildTool.toUpperCase()} build failed!`);
57
77
  throw new Error("Falha no build do Java!");
58
78
  }
79
+
80
+ if (!incremental) {
81
+ this.cache.saveCache(this.projectConfig.buildTool);
82
+ }
59
83
  }
60
84
 
61
85
  private async processBuildLogs(stream: ReadableStream, quiet: boolean) {
@@ -79,7 +103,7 @@ export class BuildService {
79
103
  errorCount++;
80
104
  if (errorCount > maxErrors && !this.projectConfig.verbose) {
81
105
  if (errorCount === maxErrors + 1) {
82
- console.log(`\n ${"\x1b[31m"}... e mais erros ocultos. Use -V para ver todos.${"\x1b[0m"}`);
106
+ Logger.warn("... e mais erros ocultos. Use -V para ver todos.");
83
107
  }
84
108
  continue;
85
109
  }
@@ -92,107 +116,91 @@ export class BuildService {
92
116
  }
93
117
 
94
118
  const summarized = Logger.summarize(cleanLine);
95
- if (summarized) console.log(summarized);
119
+ if (summarized) Logger.log(summarized);
96
120
  }
97
121
  }
98
122
  }
99
123
 
100
- async syncClasses(): Promise<string | null> {
101
- const fs = require("fs");
102
- let appFolder = this.projectConfig.appName || this.inferredAppName || "";
103
-
124
+ async syncClasses(customSrc?: string): Promise<string | null> {
125
+ const appFolder = this.projectService.getInferredAppName();
104
126
  const webappsPath = path.join(this.tomcatConfig.path, this.tomcatConfig.webapps);
105
127
 
106
- if (!appFolder && fs.existsSync(webappsPath)) {
107
- const folders = fs.readdirSync(webappsPath, { withFileTypes: true })
108
- .filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs"].includes(dirent.name));
109
-
110
- if (folders.length === 1) {
111
- appFolder = folders[0].name;
112
- } else if (folders.length > 1) {
113
- const sorted = folders.map((f: any) => ({
114
- name: f.name,
115
- time: fs.statSync(path.join(webappsPath, f.name)).mtimeMs
116
- })).sort((a: any, b: any) => b.time - a.time);
117
- appFolder = sorted[0].name;
118
- }
119
- }
120
-
121
- const sourceDir = this.projectConfig.buildTool === 'maven' ? 'target/classes' : 'build/classes/java/main';
122
- const destDir = path.join(webappsPath, appFolder, "WEB-INF", "classes");
128
+ const sourceDir = customSrc || this.projectService.getClassesDir();
129
+ const destDir = customSrc ? path.join(webappsPath, appFolder) : path.join(webappsPath, appFolder, "WEB-INF", "classes");
123
130
 
124
- if (!fs.existsSync(sourceDir)) return null;
125
-
126
- if (!appFolder || !fs.existsSync(destDir)) {
127
- Logger.warn("Pasta descompactada no Tomcat não encontrada. Hot Swap impossível.");
128
- return null;
131
+ if (!existsSync(sourceDir)) return null;
132
+ if (!appFolder || !existsSync(destDir)) {
133
+ if (customSrc && appFolder) {
134
+ mkdirSync(destDir, { recursive: true });
135
+ } else {
136
+ return null;
137
+ }
129
138
  }
130
139
 
131
- const copyDir = (src: string, dest: string) => {
132
- if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
133
- const list = fs.readdirSync(src, { withFileTypes: true });
134
- for (const item of list) {
140
+ const fastSync = async (src: string, dest: string) => {
141
+ if (!existsSync(dest)) await fs.mkdir(dest, { recursive: true });
142
+ const list = await fs.readdir(src, { withFileTypes: true });
143
+
144
+ const tasks = list.map(async (item) => {
135
145
  const s = path.join(src, item.name);
136
146
  const d = path.join(dest, item.name);
147
+
137
148
  if (item.isDirectory()) {
138
- copyDir(s, d);
149
+ await fastSync(s, d);
139
150
  } else {
140
- if (!fs.existsSync(d) || fs.statSync(s).mtimeMs > fs.statSync(d).mtimeMs) {
141
- fs.copyFileSync(s, d);
151
+ const sStat = await fs.stat(s);
152
+ let shouldCopy = false;
153
+
154
+ if (!existsSync(d)) {
155
+ shouldCopy = true;
156
+ } else {
157
+ const dStat = await fs.stat(d);
158
+ if (sStat.mtimeMs > dStat.mtimeMs || dStat.size === 0) {
159
+ shouldCopy = true;
160
+ }
161
+ }
162
+
163
+ if (shouldCopy) {
164
+ let retries = 3;
165
+ while (retries > 0) {
166
+ try {
167
+ await fs.copyFile(s, d);
168
+ const finalStat = await fs.stat(d);
169
+ if (item.name.endsWith(".jar") && finalStat.size === 0 && sStat.size > 0) {
170
+ throw new Error("Zero byte copy detected");
171
+ }
172
+ await fs.utimes(d, sStat.atime, sStat.mtime);
173
+ break;
174
+ } catch (e) {
175
+ retries--;
176
+ if (retries === 0) {
177
+ Logger.warn(`Failed to copy ${item.name} after retries.`);
178
+ } else {
179
+ await new Promise(r => setTimeout(r, 100));
180
+ }
181
+ }
182
+ }
142
183
  }
143
184
  }
144
- }
185
+ });
186
+
187
+ await Promise.all(tasks);
145
188
  };
146
189
 
147
- copyDir(sourceDir, destDir);
148
- Logger.success("Classes swapped in running Tomcat");
190
+ await fastSync(sourceDir, destDir);
149
191
  return appFolder;
150
192
  }
151
193
 
152
- async deployToWebapps(): Promise<string> {
153
- const destDir = path.join(this.tomcatConfig.path, this.tomcatConfig.webapps);
154
-
194
+ async deployToWebapps(): Promise<{ path: string, finalName: string, isDirectory: boolean }> {
155
195
  Logger.step("Searching for generated artifacts");
156
196
 
157
- const findWars = (dir: string): string[] => {
158
- let results: string[] = [];
159
- const list = readdirSync(dir, { withFileTypes: true });
160
- for (const item of list) {
161
- const res = path.resolve(dir, item.name);
162
- if (item.isDirectory()) {
163
- if (item.name === 'target' || item.name === 'build') {
164
- results = results.concat(findWars(res));
165
- } else if (!['node_modules', '.git', 'src', 'webapps', 'bin', 'conf', 'lib', 'logs', 'temp', 'work'].includes(item.name)) {
166
- results = results.concat(findWars(res));
167
- }
168
- } else if (item.name.endsWith('.war')) {
169
- results.push(res);
170
- }
171
- }
172
- return results;
173
- };
174
-
175
- const allWars = findWars(process.cwd())
176
- .map(f => ({ path: f, name: path.basename(f), time: statSync(f).mtime.getTime() }))
177
- .sort((a, b) => b.time - a.time);
178
-
179
- if (allWars.length === 0) {
180
- throw new Error('Nenhum arquivo .war encontrado! Verifique se o build realmente gerou um artefato.');
181
- }
182
-
183
- const warFile = allWars[0];
184
- const finalName = this.projectConfig.appName ? `${this.projectConfig.appName}.war` : warFile.name;
197
+ const artifact = this.projectService.getArtifact();
185
198
 
186
199
  if (!this.projectConfig.quiet) {
187
- Logger.info("Artifact", warFile.name);
188
- if (this.projectConfig.appName) Logger.info("Deploy as", finalName);
189
- } else {
190
- const displayName = this.projectConfig.appName ? `${this.projectConfig.appName}` : warFile.name.replace(".war", "");
191
- process.stdout.write(` ${"\x1b[90m"}➜${"\x1b[0m"} Deploying ${"\x1b[1m"}${displayName}${"\x1b[0m"}...\n`);
200
+ Logger.info(artifact.isDirectory ? "Exploded Dir" : "Artifact", path.basename(artifact.path));
201
+ if (this.projectConfig.appName) Logger.info("Deploy as", artifact.name);
192
202
  }
193
203
 
194
- copyFileSync(warFile.path, path.join(destDir, finalName));
195
- this.inferredAppName = finalName.replace(".war", "");
196
- return finalName;
204
+ return { path: artifact.path, finalName: artifact.name, isDirectory: artifact.isDirectory };
197
205
  }
198
206
  }
@@ -18,6 +18,23 @@ export class EndpointService {
18
18
  const content = readFileSync(res, 'utf8');
19
19
  const fileEndpoints = this.parseJavaFile(content, item.name, contextPath);
20
20
  endpoints.push(...fileEndpoints);
21
+ } else if (item.name.endsWith('.jsp')) {
22
+ const parts = res.split(/[/\\]/);
23
+ const webappIndex = parts.indexOf("webapp");
24
+ const webContentIndex = parts.indexOf("WebContent");
25
+ const rootIndex = webappIndex !== -1 ? webappIndex : webContentIndex;
26
+
27
+ if (rootIndex !== -1) {
28
+ const relPath = "/" + parts.slice(rootIndex + 1).join("/");
29
+ endpoints.push({
30
+ method: "GET",
31
+ path: relPath,
32
+ fullPath: this.combinePaths(contextPath, relPath),
33
+ className: "JSP",
34
+ methodName: item.name,
35
+ parameters: []
36
+ });
37
+ }
21
38
  }
22
39
  }
23
40
  };
@@ -33,11 +50,9 @@ export class EndpointService {
33
50
  const endpoints: ApiEndpoint[] = [];
34
51
  const className = fileName.replace(".java", "");
35
52
 
36
- // Find class-level mapping
37
53
  const classPathMatch = content.match(/@(Path|RequestMapping)\s*\(\s*["'](.*?)["']\s*\)/);
38
54
  const basePath = classPathMatch ? this.normalizePath(classPathMatch[2]) : "";
39
55
 
40
- // Common mapping annotations
41
56
  const methodRegex = /@(GET|POST|PUT|DELETE|PATCH|Path|RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\s*(\(\s*["'](.*?)["']\s*\))?\s*([\s\S]*?)\s+([a-zA-Z0-9_]+)\s*\(([\s\S]*?)\)/g;
42
57
 
43
58
  let match;
@@ -98,7 +113,6 @@ export class EndpointService {
98
113
  for (const p of individualParams) {
99
114
  const trimmed = p.trim();
100
115
 
101
- // Check for annotations
102
116
  const pathParam = trimmed.match(/@PathParam\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
103
117
  const pathVariable = trimmed.match(/@PathVariable\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
104
118
  const queryParam = trimmed.match(/@QueryParam\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
@@ -0,0 +1,126 @@
1
+ import { existsSync, readdirSync, statSync } from "fs";
2
+ import path from "path";
3
+ import type { ProjectConfig } from "../types/config";
4
+
5
+ export class ProjectService {
6
+ constructor(private config: ProjectConfig) {}
7
+
8
+ getBuildOutputDir(): string {
9
+ return path.join(process.cwd(), this.config.buildTool === "maven" ? "target" : "build");
10
+ }
11
+
12
+ getClassesDir(): string {
13
+ return this.config.buildTool === "maven"
14
+ ? path.join(process.cwd(), "target", "classes")
15
+ : path.join(process.cwd(), "build", "classes", "java", "main");
16
+ }
17
+
18
+ getSourceDirs(): string[] {
19
+ return [
20
+ path.join(process.cwd(), "src", "main", "java"),
21
+ path.join(process.cwd(), "src", "main", "resources"),
22
+ path.join(process.cwd(), "src", "main", "webapp")
23
+ ].filter(d => existsSync(d));
24
+ }
25
+
26
+ getArtifact(): { path: string; name: string; isDirectory: boolean } {
27
+ const buildDir = this.getBuildOutputDir();
28
+ const artifacts = this.searchArtifacts(buildDir).sort((a, b) => b.time - a.time);
29
+
30
+ if (artifacts.length === 0) {
31
+ throw new Error(`Nenhum artefato (.war ou pasta exploded) encontrado em ${buildDir}!`);
32
+ }
33
+
34
+ const artifact = artifacts[0];
35
+ return {
36
+ path: artifact.path,
37
+ name: this.config.appName ? `${this.config.appName}.war` : artifact.name,
38
+ isDirectory: artifact.isDirectory
39
+ };
40
+ }
41
+
42
+ private searchArtifacts(dir: string): { path: string; name: string; time: number; isDirectory: boolean }[] {
43
+ let results: { path: string; name: string; time: number; isDirectory: boolean }[] = [];
44
+ if (!existsSync(dir)) return results;
45
+
46
+ const list = readdirSync(dir, { withFileTypes: true });
47
+ for (const item of list) {
48
+ const fullPath = path.resolve(dir, item.name);
49
+
50
+ if (item.isDirectory()) {
51
+ // Se for Maven e tiver WEB-INF, é um exploded war
52
+ if (this.config.buildTool === 'maven' && existsSync(path.join(fullPath, "WEB-INF"))) {
53
+ results.push({
54
+ path: fullPath,
55
+ name: `${item.name}.war`,
56
+ time: statSync(fullPath).mtime.getTime(),
57
+ isDirectory: true
58
+ });
59
+ } else if (item.name.endsWith('.war')) { // Algumas ferramentas podem gerar pastas .war
60
+ results.push({
61
+ path: fullPath,
62
+ name: item.name,
63
+ time: statSync(fullPath).mtime.getTime(),
64
+ isDirectory: true
65
+ });
66
+ } else if (['libs', 'distributions'].includes(item.name)) { // Gradle common dirs
67
+ results = results.concat(this.searchArtifacts(fullPath));
68
+ }
69
+ } else if (item.name.endsWith('.war')) {
70
+ results.push({
71
+ path: fullPath,
72
+ name: item.name,
73
+ time: statSync(fullPath).mtime.getTime(),
74
+ isDirectory: false
75
+ });
76
+ }
77
+ }
78
+ return results;
79
+ }
80
+
81
+ getInferredAppName(): string {
82
+ if (this.config.appName) return this.config.appName;
83
+ try {
84
+ const artifact = this.getArtifact();
85
+ return artifact.name.replace(".war", "");
86
+ } catch (e) {
87
+ return "ROOT";
88
+ }
89
+ }
90
+
91
+ findAllClassPaths(): string[] {
92
+ const results: string[] = [];
93
+ const root = process.cwd();
94
+
95
+ const scan = (dir: string) => {
96
+ try {
97
+ const files = readdirSync(dir, { withFileTypes: true });
98
+ for (const file of files) {
99
+ if (!file.isDirectory()) continue;
100
+
101
+ const name = file.name;
102
+ if (name.startsWith('.') || ['node_modules', 'out', 'bin', 'src', 'webapps', '.xavva'].includes(name)) continue;
103
+
104
+ const fullPath = path.join(dir, name);
105
+
106
+ const isMavenClasses = this.config.buildTool === 'maven' && name === 'classes' && dir.endsWith('target');
107
+ const isGradleClasses = this.config.buildTool === 'gradle' && name === 'main' && dir.endsWith(path.join('classes', 'java'));
108
+
109
+ if (isMavenClasses || isGradleClasses) {
110
+ results.push(fullPath.replace(/\\/g, "/"));
111
+ } else {
112
+ scan(fullPath);
113
+ }
114
+ }
115
+ } catch (e) {}
116
+ };
117
+
118
+ scan(root);
119
+
120
+ if (results.length === 0) {
121
+ results.push(this.getClassesDir().replace(/\\/g, "/"));
122
+ }
123
+
124
+ return results;
125
+ }
126
+ }
@@ -1,17 +1,24 @@
1
- import type { TomcatConfig } from "../types/config";
1
+ import type { TomcatConfig, AppConfig } from "../types/config";
2
2
  import { Logger } from "../utils/ui";
3
+ import type { Subprocess } from "bun";
4
+ import { ProjectService } from "./ProjectService";
3
5
 
4
6
  export class TomcatService {
5
7
  private activeConfig: TomcatConfig;
6
- private currentProcess: any = null;
8
+ private currentProcess: Subprocess | null = null;
7
9
  private stopStartupSpinner?: (success?: boolean) => void;
8
10
  public onReady?: () => void;
9
11
  private pid: number | null = null;
12
+ private projectService: ProjectService | null = null;
10
13
 
11
14
  constructor(customConfig: TomcatConfig) {
12
15
  this.activeConfig = customConfig;
13
16
  }
14
17
 
18
+ setProjectService(projectService: ProjectService) {
19
+ this.projectService = projectService;
20
+ }
21
+
15
22
  async getMemoryUsage(): Promise<string> {
16
23
  if (!this.pid) return "0 MB";
17
24
  try {
@@ -35,7 +42,7 @@ export class TomcatService {
35
42
  }
36
43
  }
37
44
 
38
- clearWebapps(appName?: string) {
45
+ clearWebapps() {
39
46
  const fs = require("fs");
40
47
  const path = require("path");
41
48
  const webappsPath = path.join(this.activeConfig.path, "webapps");
@@ -70,13 +77,99 @@ export class TomcatService {
70
77
  }
71
78
  }
72
79
 
73
- start(cleanLogs: boolean = false, debug: boolean = false, skipScan: boolean = false, quiet: boolean = false) {
80
+ private async ensureHotswapAgent(): Promise<string | null> {
81
+ const fs = require("fs");
82
+ const path = require("path");
83
+ const os = require("os");
84
+ const agentDir = path.join(os.homedir(), ".xavva", "agents");
85
+ const agentPath = path.join(agentDir, "hotswap-agent-2.0.3.jar");
86
+
87
+ if (fs.existsSync(agentPath) && fs.statSync(agentPath).size > 1000) return agentPath;
88
+
89
+ try {
90
+ if (!fs.existsSync(agentDir)) fs.mkdirSync(agentDir, { recursive: true });
91
+
92
+ Logger.step("Downloading HotswapAgent v2.0.3 (Global)...");
93
+ const url = "https://github.com/HotswapProjects/HotswapAgent/releases/download/RELEASE-2.0.3/hotswap-agent-2.0.3.jar";
94
+ const response = await fetch(url);
95
+ if (!response.ok) throw new Error(`Status: ${response.status}`);
96
+
97
+ const buffer = await response.arrayBuffer();
98
+ fs.writeFileSync(agentPath, Buffer.from(buffer));
99
+ Logger.success("HotswapAgent v2.0.3 installed globally!");
100
+ return agentPath;
101
+ } catch (e) {
102
+ Logger.warn("Falha ao baixar HotswapAgent. Usando hot swap padrão da JVM.");
103
+ return null;
104
+ }
105
+ }
106
+
107
+ async start(config: AppConfig, isWatching: boolean = false) {
74
108
  const binPath = `${this.activeConfig.path}\\bin\\catalina.bat`;
75
- const args = debug ? ["jpda", "run"] : ["run"];
109
+ const args = (config.project.debug || isWatching) ? ["jpda", "run"] : ["run"];
76
110
 
77
111
  const catalinaOpts = [process.env.CATALINA_OPTS || ""];
78
112
 
79
- if (skipScan) {
113
+ if (config.project.debug || isWatching) {
114
+ const agentPath = await this.ensureHotswapAgent();
115
+ if (agentPath) {
116
+ catalinaOpts.push(`-javaagent:${agentPath}`);
117
+
118
+ let javaBin = "java";
119
+ if (process.env.JAVA_HOME) {
120
+ javaBin = require("path").join(process.env.JAVA_HOME, "bin", "java.exe");
121
+ }
122
+
123
+ const javaVer = Bun.spawnSync([javaBin, "-version"]);
124
+ const output = (javaVer.stderr.toString() + javaVer.stdout.toString()).toLowerCase();
125
+
126
+ if (output.includes("dcevm") || output.includes("jbr") || output.includes("trava")) {
127
+ catalinaOpts.push("-XX:+AllowEnhancedClassRedefinition");
128
+ }
129
+
130
+ catalinaOpts.push(
131
+ "--add-opens=java.base/jdk.internal.loader=ALL-UNNAMED",
132
+ "--add-opens=java.base/java.lang=ALL-UNNAMED",
133
+ "--add-opens=java.base/java.io=ALL-UNNAMED",
134
+ "--add-opens=java.base/java.net=ALL-UNNAMED",
135
+ "--add-opens=java.base/java.util=ALL-UNNAMED",
136
+ "--add-opens=java.base/java.util.concurrent=ALL-UNNAMED",
137
+ "--add-opens=java.base/java.security=ALL-UNNAMED",
138
+ "--add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED",
139
+ "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
140
+ "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
141
+ "--add-opens=java.base/java.util.jar=ALL-UNNAMED",
142
+ "--add-opens=java.desktop/java.beans=ALL-UNNAMED"
143
+ );
144
+
145
+ const fs = require("fs");
146
+ const path = require("path");
147
+ const xavvaDir = path.join(process.cwd(), ".xavva");
148
+ if (!fs.existsSync(xavvaDir)) fs.mkdirSync(xavvaDir, { recursive: true });
149
+
150
+ const propsPath = path.join(xavvaDir, "hotswap-agent.properties");
151
+ const propsContent = `autoHotswap=true\nautoHotswap.delay=500\nwatchResources=false\nLOGGER=info`;
152
+ fs.writeFileSync(propsPath, propsContent);
153
+
154
+ catalinaOpts.push(`-Dhotswap-agent.properties.path=${propsPath}`);
155
+
156
+ if (this.projectService) {
157
+ const classPaths = this.projectService.findAllClassPaths();
158
+ if (classPaths.length > 0) {
159
+ catalinaOpts.push(`-Dhotswap.extraClasspath=${classPaths.join(",")}`);
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ // Otimizações para JSP e Debug de JSP
166
+ catalinaOpts.push(
167
+ "-Dorg.apache.jasper.compiler.development=true",
168
+ "-Dorg.apache.jasper.compiler.disableSmap=false",
169
+ "-Dorg.apache.jasper.compiler.classdebuginfo=true"
170
+ );
171
+
172
+ if (config.project.skipScan) {
80
173
  catalinaOpts.push(
81
174
  "-Dtomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar",
82
175
  "-Dtomcat.util.scan.StandardJarScanFilter.jarsToScan=",
@@ -87,35 +180,44 @@ export class TomcatService {
87
180
  );
88
181
  }
89
182
 
90
- const env: any = {
183
+ const env: Record<string, string | undefined> = {
91
184
  ...process.env,
92
185
  CATALINA_HOME: this.activeConfig.path,
93
186
  CATALINA_OPTS: catalinaOpts.join(" ").trim()
94
187
  };
95
188
 
96
- if (debug) {
97
- Logger.warn("🐞 Java Debugger habilitado na porta 5005");
98
- env.JPDA_ADDRESS = "5005";
189
+ if (process.env.JAVA_HOME) {
190
+ env.JAVA_HOME = process.env.JAVA_HOME;
191
+ env.JRE_HOME = process.env.JAVA_HOME;
192
+ }
193
+
194
+ if (config.project.debug) {
195
+ Logger.debug(`Java Debugger habilitado na porta ${config.project.debugPort}`);
196
+ env.JPDA_ADDRESS = String(config.project.debugPort);
99
197
  env.JPDA_TRANSPORT = "dt_socket";
100
198
  }
101
199
 
102
- if (cleanLogs || quiet) {
200
+ if ((config.project.cleanLogs || config.project.quiet) && !config.project.verbose) {
103
201
  this.stopStartupSpinner = Logger.spinner("Starting Tomcat server");
104
202
  }
105
203
 
106
204
  this.currentProcess = Bun.spawn([binPath, ...args], {
107
205
  stdout: "pipe",
108
206
  stderr: "pipe",
109
- env: env
207
+ env: env as any
110
208
  });
111
209
 
112
210
  this.pid = this.currentProcess.pid;
113
211
 
114
- this.processLogStream(this.currentProcess.stdout, cleanLogs, quiet);
115
- this.processLogStream(this.currentProcess.stderr, cleanLogs, quiet);
212
+ if (this.currentProcess.stdout) {
213
+ this.processLogStream(this.currentProcess.stdout as any, config.project.cleanLogs, config.project.quiet, config.project.verbose, config.tomcat.grep || "");
214
+ }
215
+ if (this.currentProcess.stderr) {
216
+ this.processLogStream(this.currentProcess.stderr as any, config.project.cleanLogs, config.project.quiet, config.project.verbose, config.tomcat.grep || "");
217
+ }
116
218
  }
117
219
 
118
- private async processLogStream(stream: ReadableStream, clean: boolean, quiet: boolean) {
220
+ private async processLogStream(stream: ReadableStream, clean: boolean, quiet: boolean, verbose: boolean, grep: string) {
119
221
  const reader = stream.getReader();
120
222
  const decoder = new TextDecoder();
121
223
 
@@ -141,6 +243,11 @@ export class TomcatService {
141
243
  }
142
244
  }
143
245
 
246
+ if (verbose) {
247
+ Logger.log(cleanLine);
248
+ continue;
249
+ }
250
+
144
251
  if (clean) {
145
252
  if (quiet && !Logger.isEssential(cleanLine)) {
146
253
  if (Logger.isSystemNoise(cleanLine)) continue;
@@ -149,14 +256,14 @@ export class TomcatService {
149
256
  continue;
150
257
  }
151
258
 
152
- if (this.activeConfig.grep && !cleanLine.toLowerCase().includes(this.activeConfig.grep.toLowerCase())) {
259
+ if (grep && !cleanLine.toLowerCase().includes(grep.toLowerCase())) {
153
260
  if (!Logger.isEssential(cleanLine)) continue;
154
261
  }
155
262
 
156
263
  const summarized = Logger.summarize(cleanLine);
157
- console.log(summarized);
264
+ if (summarized) Logger.log(summarized);
158
265
  } else {
159
- console.log(cleanLine);
266
+ Logger.log(cleanLine);
160
267
  }
161
268
  }
162
269
  }