@archznn/xavva 2.4.0 → 2.5.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/package.json
CHANGED
|
@@ -14,6 +14,7 @@ export class DeployCommand implements Command {
|
|
|
14
14
|
async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
15
15
|
const incremental = args?.watch && args?.incremental;
|
|
16
16
|
const isWatching = !!args?.watch;
|
|
17
|
+
const changedFiles = args?.changedFiles;
|
|
17
18
|
const tomcat = this.tomcat;
|
|
18
19
|
const builder = this.builder;
|
|
19
20
|
|
|
@@ -46,11 +47,11 @@ export class DeployCommand implements Command {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
if (incremental) {
|
|
49
|
-
const actualAppFolder = await builder.syncClasses();
|
|
50
|
+
const actualAppFolder = await builder.syncClasses(changedFiles);
|
|
50
51
|
const actualContextPath = contextPath || actualAppFolder || "";
|
|
51
52
|
const actualAppUrl = `http://localhost:${config.tomcat.port}/${actualContextPath}`;
|
|
52
53
|
await BrowserService.reload(actualAppUrl);
|
|
53
|
-
Logger.success(
|
|
54
|
+
Logger.success(`redeploy completed (${changedFiles?.length || 'all'} file(s))`);
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -98,7 +99,7 @@ export class DeployCommand implements Command {
|
|
|
98
99
|
const finalAppUrl = `http://localhost:${config.tomcat.port}/${finalContextPath}`;
|
|
99
100
|
|
|
100
101
|
tomcat.onReady = async () => {
|
|
101
|
-
await this.handleServerReady(config, finalAppUrl, finalContextPath, tomcat, incremental);
|
|
102
|
+
await this.handleServerReady(config, finalAppUrl, finalContextPath, tomcat, !!incremental);
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
tomcat.start(config, isWatching);
|
|
@@ -129,19 +129,121 @@ export class BuildService {
|
|
|
129
129
|
await this.fastSync(srcDir, destDir);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
async syncClasses(
|
|
132
|
+
async syncClasses(changedFiles?: string[]): Promise<string | null> {
|
|
133
133
|
const appFolder = this.projectService.getInferredAppName();
|
|
134
134
|
const webappPath = path.join(this.tomcatConfig.path, "webapps", appFolder);
|
|
135
135
|
const targetLib = path.join(webappPath, "WEB-INF", "classes");
|
|
136
|
-
const sourceDir =
|
|
136
|
+
const sourceDir = this.projectService.getClassesDir();
|
|
137
137
|
|
|
138
138
|
if (!existsSync(sourceDir)) return null;
|
|
139
139
|
if (!existsSync(targetLib)) mkdirSync(targetLib, { recursive: true });
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
// Se temos uma lista específica de arquivos modificados, sincroniza apenas eles
|
|
142
|
+
if (changedFiles && changedFiles.length > 0) {
|
|
143
|
+
await this.syncSpecificFiles(changedFiles, sourceDir, targetLib);
|
|
144
|
+
} else {
|
|
145
|
+
// Caso contrário, sincroniza tudo (comportamento padrão)
|
|
146
|
+
await this.fastSync(sourceDir, targetLib);
|
|
147
|
+
}
|
|
148
|
+
|
|
142
149
|
return appFolder;
|
|
143
150
|
}
|
|
144
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Sincroniza apenas arquivos específicos baseado nos arquivos .java modificados.
|
|
154
|
+
* Converte .java para .class e sincroniza apenas os arquivos realmente modificados.
|
|
155
|
+
*/
|
|
156
|
+
private async syncSpecificFiles(changedFiles: string[], sourceDir: string, targetLib: string): Promise<void> {
|
|
157
|
+
const tasks: Promise<void>[] = [];
|
|
158
|
+
const syncedCount = { value: 0 };
|
|
159
|
+
|
|
160
|
+
for (const javaFile of changedFiles) {
|
|
161
|
+
// Converte caminho do .java para caminho do .class
|
|
162
|
+
// Ex: src/main/java/com/example/Foo.java -> target/classes/com/example/Foo.class
|
|
163
|
+
const relativePath = this.javaToClassPath(javaFile);
|
|
164
|
+
if (!relativePath) continue;
|
|
165
|
+
|
|
166
|
+
const sourcePath = path.join(sourceDir, relativePath);
|
|
167
|
+
const targetPath = path.join(targetLib, relativePath);
|
|
168
|
+
|
|
169
|
+
if (!existsSync(sourcePath)) {
|
|
170
|
+
// Se o .class não existe, talvez seja um arquivo excluído ou inner class
|
|
171
|
+
// Neste caso, faz sync completo como fallback
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
tasks.push((async () => {
|
|
176
|
+
const targetDir = path.dirname(targetPath);
|
|
177
|
+
if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
|
|
178
|
+
|
|
179
|
+
const srcStat = statSync(sourcePath);
|
|
180
|
+
const destStat = existsSync(targetPath) ? statSync(targetPath) : null;
|
|
181
|
+
|
|
182
|
+
if (!destStat || srcStat.mtimeMs > destStat.mtimeMs) {
|
|
183
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
184
|
+
syncedCount.value++;
|
|
185
|
+
}
|
|
186
|
+
})());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await Promise.all(tasks);
|
|
190
|
+
|
|
191
|
+
// Se não conseguimos sincronizar nenhum arquivo específico, faz sync completo
|
|
192
|
+
if (syncedCount.value === 0) {
|
|
193
|
+
await this.fastSync(sourceDir, targetLib);
|
|
194
|
+
} else if (!this.projectConfig.quiet) {
|
|
195
|
+
Logger.info("sync", `${syncedCount.value} classe(s) sincronizada(s)`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Converte caminho de arquivo .java para caminho relativo de .class
|
|
201
|
+
*/
|
|
202
|
+
private javaToClassPath(javaFile: string): string | null {
|
|
203
|
+
// Remove prefixos comuns de diretórios source
|
|
204
|
+
const parts = javaFile.split(/[/\\]/);
|
|
205
|
+
|
|
206
|
+
// Encontra o índice após "java" ou "src/main/java" ou "src"
|
|
207
|
+
let startIndex = -1;
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < parts.length; i++) {
|
|
210
|
+
if (parts[i] === "java" && i > 0 && (parts[i-1] === "main" || parts[i-1] === "test")) {
|
|
211
|
+
startIndex = i + 1;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Se não encontrou padrão maven, tenta achar "src"
|
|
217
|
+
if (startIndex === -1) {
|
|
218
|
+
const srcIndex = parts.indexOf("src");
|
|
219
|
+
if (srcIndex !== -1 && srcIndex < parts.length - 1) {
|
|
220
|
+
// Pula "src" e possível "main/java"
|
|
221
|
+
if (parts[srcIndex + 1] === "main" && parts[srcIndex + 2] === "java") {
|
|
222
|
+
startIndex = srcIndex + 3;
|
|
223
|
+
} else {
|
|
224
|
+
startIndex = srcIndex + 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Se ainda não encontrou, assume que o caminho já é relativo ao package
|
|
230
|
+
if (startIndex === -1) {
|
|
231
|
+
startIndex = 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Pega o caminho relativo
|
|
235
|
+
const relativeParts = parts.slice(startIndex);
|
|
236
|
+
if (relativeParts.length === 0) return null;
|
|
237
|
+
|
|
238
|
+
// Substitui extensão .java por .class
|
|
239
|
+
const fileName = relativeParts[relativeParts.length - 1];
|
|
240
|
+
if (!fileName || !fileName.endsWith(".java")) return null;
|
|
241
|
+
|
|
242
|
+
relativeParts[relativeParts.length - 1] = fileName.replace(".java", ".class");
|
|
243
|
+
|
|
244
|
+
return path.join(...relativeParts);
|
|
245
|
+
}
|
|
246
|
+
|
|
145
247
|
private async fastSync(src: string, dest: string) {
|
|
146
248
|
const entries = readdirSync(src, { withFileTypes: true });
|
|
147
249
|
|
|
@@ -9,6 +9,11 @@ export class WatcherService {
|
|
|
9
9
|
private pendingFullBuild = false;
|
|
10
10
|
private coolingFiles = new Set<string>();
|
|
11
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;
|
|
12
17
|
|
|
13
18
|
constructor(private config: AppConfig, private deployCmd: DeployCommand) {}
|
|
14
19
|
|
|
@@ -43,24 +48,57 @@ export class WatcherService {
|
|
|
43
48
|
if (!isJava) return;
|
|
44
49
|
|
|
45
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
|
+
|
|
46
62
|
clearTimeout(this.debounceTimer);
|
|
47
63
|
|
|
48
64
|
this.debounceTimer = setTimeout(() => {
|
|
49
|
-
|
|
65
|
+
const filesToCompile = [...this.modifiedFiles];
|
|
66
|
+
this.modifiedFiles.clear();
|
|
67
|
+
this.run(this.pendingFullBuild ? false : true, filesToCompile);
|
|
50
68
|
this.pendingFullBuild = false;
|
|
51
69
|
}, WATCHER_DEBOUNCE_MS);
|
|
52
70
|
});
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
private async run(incremental = false) {
|
|
73
|
+
private async run(incremental = false, changedFiles?: string[]) {
|
|
56
74
|
if (this.isDeploying) return;
|
|
57
75
|
this.isDeploying = true;
|
|
76
|
+
|
|
58
77
|
try {
|
|
59
|
-
|
|
78
|
+
// Passa os arquivos específicos que foram modificados
|
|
79
|
+
await this.deployCmd.execute(this.config, {
|
|
80
|
+
watch: true,
|
|
81
|
+
incremental,
|
|
82
|
+
changedFiles
|
|
83
|
+
});
|
|
60
84
|
} catch (e) {
|
|
61
85
|
// Error handled by command
|
|
62
86
|
} finally {
|
|
63
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
|
+
}
|
|
64
102
|
}
|
|
65
103
|
}
|
|
66
104
|
|
package/src/types/config.ts
CHANGED
package/src/utils/constants.ts
CHANGED
|
@@ -10,8 +10,8 @@ export const DEFAULT_DEBUG_PORT = 5005;
|
|
|
10
10
|
|
|
11
11
|
// Timeouts (em milissegundos)
|
|
12
12
|
export const TIMEOUT_SHUTDOWN_MS = 5000;
|
|
13
|
-
export const WATCHER_DEBOUNCE_MS =
|
|
14
|
-
export const WATCHER_COOLING_MS =
|
|
13
|
+
export const WATCHER_DEBOUNCE_MS = 1500;
|
|
14
|
+
export const WATCHER_COOLING_MS = 1000;
|
|
15
15
|
export const BROWSER_OPEN_DELAY_MS = 800;
|
|
16
16
|
export const DEPLOY_HEALTH_CHECK_DELAY_MS = 1500;
|
|
17
17
|
export const HOTSWAP_DELAY_MS = 500;
|