Skip to content

Commit 8744ef1

Browse files
authored
Merge pull request #15 from timonmdy/rolling/8
Dynamic JARs
2 parents 637070f + b06792e commit 8744ef1

43 files changed

Lines changed: 1343 additions & 936 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "java-runner-client",
3-
"version": "2.1.4",
3+
"version": "2.1.5",
44
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
55
"main": "dist/main/main.js",
66
"scripts": {

src/main/ProcessManager.ts

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ import {
1111
} from './shared/types/Process.types';
1212
import { Profile } from './shared/types/Profile.types';
1313
import { ProcessIPC } from './ipc/Process.ipc';
14+
import { DEFAULT_JAR_RESOLUTION } from './shared/config/JarResolution.config';
15+
16+
// Inline resolution to avoid circular IPC dependency
17+
import fs from 'fs';
18+
import { patternToRegex } from './shared/config/JarResolution.config';
19+
import type { JarResolutionConfig } from './shared/types/JarResolution.types';
20+
import { ProfileIPC } from './ipc/Profile.ipc';
21+
import { saveProfile } from './Store';
1422

1523
const SELF_PROCESS_NAME = 'Java Client Runner';
1624

@@ -34,6 +42,83 @@ interface ManagedProcess {
3442
intentionallyStopped: boolean;
3543
}
3644

45+
function parseVersion(str: string): number[] {
46+
return str
47+
.split(/[.\-_]/)
48+
.map((p) => parseInt(p, 10))
49+
.filter((n) => !isNaN(n));
50+
}
51+
52+
function compareVersionArrays(a: number[], b: number[]): number {
53+
const len = Math.max(a.length, b.length);
54+
for (let i = 0; i < len; i++) {
55+
const diff = (b[i] ?? 0) - (a[i] ?? 0);
56+
if (diff !== 0) return diff;
57+
}
58+
return 0;
59+
}
60+
61+
function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
62+
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
63+
64+
if (!res.enabled) {
65+
return { jarPath: profile.jarPath };
66+
}
67+
68+
if (!res.baseDir) return { jarPath: '', error: 'Dynamic JAR: no base directory set.' };
69+
70+
let entries: fs.Dirent[];
71+
try {
72+
entries = fs.readdirSync(res.baseDir, { withFileTypes: true });
73+
} catch {
74+
return { jarPath: '', error: `Dynamic JAR: cannot read directory "${res.baseDir}".` };
75+
}
76+
77+
const jars = entries.filter((e) => e.isFile() && e.name.endsWith('.jar'));
78+
79+
let matchRegex: RegExp;
80+
if (res.strategy === 'regex' && res.regexOverride?.trim()) {
81+
try {
82+
matchRegex = new RegExp(res.regexOverride.trim(), 'i');
83+
} catch {
84+
return { jarPath: '', error: 'Dynamic JAR: invalid regular expression.' };
85+
}
86+
} else {
87+
matchRegex = patternToRegex(res.pattern);
88+
}
89+
90+
const matched = jars.filter((e) => matchRegex.test(e.name));
91+
if (matched.length === 0) {
92+
return { jarPath: '', error: 'Dynamic JAR: no files matched the pattern.' };
93+
}
94+
95+
let chosen: string;
96+
97+
if (res.strategy === 'latest-modified') {
98+
const withMtime = matched.map((e) => {
99+
const full = path.join(res.baseDir, e.name);
100+
try {
101+
return { name: e.name, mtime: fs.statSync(full).mtimeMs };
102+
} catch {
103+
return { name: e.name, mtime: 0 };
104+
}
105+
});
106+
chosen = withMtime.sort((a, b) => b.mtime - a.mtime)[0].name;
107+
} else if (res.strategy === 'regex') {
108+
chosen = matched[0].name;
109+
} else {
110+
const versionRegex = patternToRegex(res.pattern);
111+
const withVersions = matched.map((e) => {
112+
const m = versionRegex.exec(e.name);
113+
return { name: e.name, version: parseVersion(m?.[1] ?? '') };
114+
});
115+
withVersions.sort((a, b) => compareVersionArrays(a.version, b.version));
116+
chosen = withVersions[0].name;
117+
}
118+
119+
return { jarPath: path.join(res.baseDir, chosen) };
120+
}
121+
37122
class ProcessManager {
38123
private processes = new Map<string, ManagedProcess>();
39124
private activityLog: ProcessLogEntry[] = [];
@@ -48,27 +133,30 @@ class ProcessManager {
48133
this.window = win;
49134
}
50135

51-
private buildArgs(profile: Profile): { cmd: string; args: string[] } {
136+
private buildArgs(profile: Profile, resolvedJarPath: string): { cmd: string; args: string[] } {
52137
const cmd = profile.javaPath || 'java';
53138
const args: string[] = [];
54139
for (const a of profile.jvmArgs) if (a.enabled && a.value.trim()) args.push(a.value.trim());
55140
for (const p of profile.systemProperties)
56141
if (p.enabled && p.key.trim())
57142
args.push(p.value.trim() ? `-D${p.key.trim()}=${p.value.trim()}` : `-D${p.key.trim()}`);
58-
args.push('-jar', profile.jarPath);
143+
args.push('-jar', resolvedJarPath);
59144
for (const a of profile.programArgs) if (a.enabled && a.value.trim()) args.push(a.value.trim());
60145
return { cmd, args };
61146
}
62147

63148
start(profile: Profile): { ok: boolean; error?: string } {
64149
if (this.processes.has(profile.id)) return { ok: false, error: 'Process already running' };
65-
if (!profile.jarPath) return { ok: false, error: 'No JAR file specified' };
150+
151+
const { jarPath, error: resolveError } = resolveJarPath(profile);
152+
if (resolveError) return { ok: false, error: resolveError };
153+
if (!jarPath) return { ok: false, error: 'No JAR file specified' };
66154

67155
this.cancelRestartTimer(profile.id);
68156
this.profileSnapshots.set(profile.id, profile);
69157

70-
const { cmd, args } = this.buildArgs(profile);
71-
const cwd = profile.workingDir || path.dirname(profile.jarPath);
158+
const { cmd, args } = this.buildArgs(profile, jarPath);
159+
const cwd = profile.workingDir || path.dirname(jarPath);
72160

73161
this.pushSystem('start', profile.id, 'pending', `Starting: ${cmd} ${args.join(' ')}`);
74162
this.pushSystem('info-workdir', profile.id, 'pending', `Working dir: ${cwd}`);
@@ -92,7 +180,7 @@ class ProcessManager {
92180
process: proc,
93181
profileId: profile.id,
94182
profileName: profile.name,
95-
jarPath: profile.jarPath,
183+
jarPath,
96184
startedAt: Date.now(),
97185
intentionallyStopped: false,
98186
};
@@ -110,7 +198,7 @@ class ProcessManager {
110198
id: uuidv4(),
111199
profileId: profile.id,
112200
profileName: profile.name,
113-
jarPath: profile.jarPath,
201+
jarPath,
114202
pid,
115203
startedAt: managed.startedAt,
116204
};
@@ -253,8 +341,6 @@ class ProcessManager {
253341
this.activityLog = [];
254342
}
255343

256-
// ── Process Scanner ──────────────────────────────────────────────────────────
257-
258344
private isProtected(name: string, cmd: string): boolean {
259345
return PROTECTED_PROCESS_NAMES.some(
260346
(n) =>
@@ -416,7 +502,6 @@ class ProcessManager {
416502
}
417503
}
418504

419-
// Only kills non-protected java processes
420505
killAllJava(): { ok: boolean; killed: number } {
421506
const procs = this.scanAllProcesses().filter((p) => p.isJava && !p.protected);
422507
let killed = 0;

src/main/RestAPI.routes.ts

Lines changed: 0 additions & 135 deletions
This file was deleted.

src/main/RestAPI.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,7 @@
11
import http from 'http';
2-
import { routes } from './RestAPI.routes';
3-
import { routeConfig } from './shared/config/API.config';
4-
import { getSettings } from './Store';
5-
import { REST_API_CONFIG } from './shared/config/API.config';
6-
7-
type Params = Record<string, string>;
8-
9-
// ─── Types ────────────────────────────────────────────────────────────────────
10-
11-
type CompiledRoute = {
12-
method: string;
13-
path: string;
14-
pattern: RegExp;
15-
keys: string[];
16-
handler: (ctx: {
17-
req: http.IncomingMessage;
18-
res: http.ServerResponse;
19-
params: Params;
20-
body: unknown;
21-
}) => void | Promise<void>;
22-
};
2+
import { routes } from './rest-api/_index';
3+
import { REST_API_CONFIG, routeConfig, RouteKey } from './shared/config/API.config';
4+
import { CompiledRoute, Params } from './shared/types/RestAPI.types';
235

246
// ─── Helpers ──────────────────────────────────────────────────────────────────
257

0 commit comments

Comments
 (0)