Files
openidea/src/features/run/tomcat.ts
T
l0ng-ai c3d569d3e3 feat(run): update Spring Boot resources and application policies (#603)
Running Spring Boot main-class configurations had no Update action, and incremental Java builds did not publish edited resources to the running JVM. They now share the Update action, toolbar and build runner with Tomcat: resources go through the module's Maven or Gradle build and are published to the resolved runtime classpath, including Gradle's separate jdt.ls output directory. Class updates use scoped HotSwap when available and explain the restart requirement otherwise.

Both configuration types get "On 'Update' action" and "On frame deactivation" policies. A missing policy keeps the chooser and does no deactivation update. Focus tracking debounces external changes and prevents overlapping updates.

Verified on macOS with Maven, Gradle, Tomcat 10.1 and HotSwap. Windows and Linux focus detection were not run. On Linux a native GTK dialog can count as deactivation when no webview window is focused.

Fixes #580
2026-11-01 08:35:18 +08:00

110 lines
3.5 KiB
TypeScript

import { call, commands, type RunConfig } from "@/platform/ipc";
import type { RunConfigTypeContribution } from "@/workbench/contributions";
import { updatePolicyDefaults, updatePolicyFields } from "./updatePolicies";
import { runUpdateBuild } from "./buildUpdate";
export const tomcatType: RunConfigTypeContribution = {
id: "tomcat",
group: "Tomcat Server",
label: "Local",
icon: "server",
description: "Run a WAR application in a local Tomcat server",
debugAdapter: "java-debug",
terminal: false,
defaults: {
deployment: "artifact",
httpPort: "8080",
buildArtifact: "true",
openBrowser: "true",
...updatePolicyDefaults,
},
fields: [
...updatePolicyFields(true),
{ key: "tomcatHome", label: "Tomcat installation directory", required: true, mono: true },
{
key: "deployment",
label: "Deployment",
control: "select",
options: [
{ value: "artifact", label: "Project WAR artifact (exploded)" },
{ value: "war", label: "WAR file" },
{ value: "directory", label: "Exploded directory" },
],
},
{
key: "deploymentPath",
label: "Deployment path",
mono: true,
required: true,
visible: (p) => p.deployment !== "artifact",
},
{
key: "contextPath",
label: "Context path",
mono: true,
placeholder: "/artifact-name",
validate: (value) =>
value === "/" || /^\/[\p{L}\p{N}_-]+(?:\/[\p{L}\p{N}_-]+)*$/u.test(value)
? null
: "Enter / or a context path such as /app",
},
{
key: "httpPort",
label: "HTTP port",
required: true,
validate: (value) =>
/^\d+$/.test(value) && Number(value) > 0 && Number(value) <= 65535 ? null : "Port must be between 1 and 65535",
},
{
key: "jre",
label: "JRE",
mono: true,
placeholder: "Project JDK",
hint: "Java installation directory; empty uses the project JDK",
},
{ key: "vmOptions", label: "VM options", control: "textarea", rows: 2, mono: true },
{ key: "openBrowser", label: "Open browser after launch", control: "checkbox" },
{
key: "browserUrl",
label: "Browser URL",
mono: true,
placeholder: "Server URL from the HTTP port and context path",
visible: (p) => p.openBrowser === "true",
validate: (value) => (/^https?:\/\//i.test(value) ? null : "Enter an HTTP or HTTPS URL"),
},
{
key: "buildArtifact",
label: "Before launch: build the artifact",
control: "checkbox",
visible: (p) => p.deployment === "artifact",
},
],
commandLine: () => ({ command: "catalina", args: ["run"] }),
async prepareLaunch(config) {
if (config.target) throw new Error("Tomcat Local requires the local machine");
await call(commands.runTomcatHome(config.params?.tomcatHome ?? ""));
if (config.params?.buildArtifact === "true") await buildTomcatArtifact(config);
return config;
},
};
export async function buildTomcatArtifact(config: RunConfig): Promise<void> {
const build = await call(commands.runTomcatBuild(config));
if (!build) return;
await runUpdateBuild(build);
}
export function tomcatBrowserUrl(config: RunConfig): string {
const p = config.params ?? {};
const name =
p.artifactName ||
p.deploymentPath
?.replace(/\\/g, "/")
.split("/")
.pop()
?.replace(/\.war$/i, "") ||
"";
const context = p.contextPath || (name && name !== "ROOT" ? `/${name}` : "/");
return p.browserUrl || `http://localhost:${p.httpPort || "8080"}${context === "/" ? "/" : `${context}/`}`;
}