Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "java-runner-client",
"version": "2.2.2",
"version": "2.2.3",
"description": "Run and manage Java processes with profiles, console I/O, and system tray support",
"main": "dist/main/main.js",
"scripts": {
Expand Down Expand Up @@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.0",
"concurrently": "^8.2.0",
"electron": "^35.7.5",
"electron": "^41.1.1",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"postcss": "^8.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Base.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ok } from '../core/RestAPI';
import { getAllProfiles, getSettings, saveSettings } from '../core/Store';
import { processManager } from '../core/process/ProcessManager';
import { AppSettings } from '../shared/config/Settings.config';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const BaseRoutes: RouteMap = {
status: defineRoute('status', ({ res }) =>
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Log.routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { deleteLogFile, getLogFiles, readLogFile } from '../core/process/FileLogger';
import { err, ok } from '../core/RestAPI';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const LogRoutes: RouteMap = {
logs_list: defineRoute('logs_list', ({ res, params }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Process.routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { getAllProfiles } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';
import { defineRoute, RouteMap } from '../shared/types/API.types';

export const ProcessRoutes: RouteMap = {
processes_list: defineRoute('processes_list', ({ res }) => ok(res, processManager.getStates())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/Profile.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { v4 as uuidv4 } from 'uuid';
import { processManager } from '../core/process/ProcessManager';
import { err, ok } from '../core/RestAPI';
import { deleteProfile, getAllProfiles, saveProfile } from '../core/Store';
import { defineRoute, RouteMap } from '../shared/types/API.types';
import { Profile } from '../shared/types/Profile.types';
import { defineRoute, RouteMap } from '../shared/types/RestAPI.types';

export const ProfileRoutes: RouteMap = {
profiles_list: defineRoute('profiles_list', ({ res }) => ok(res, getAllProfiles())),
Expand Down
2 changes: 1 addition & 1 deletion src/main/api/_index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RouteKey } from '../shared/config/API.config';
import { BuiltRoute, RouteMap } from '../shared/types/RestAPI.types';
import { BuiltRoute, RouteMap } from '../shared/types/API.types';

import { BaseRoutes } from './Base.routes';
import { LogRoutes } from './Log.routes';
Expand Down
18 changes: 9 additions & 9 deletions src/main/core/IPCController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
*
* Defining a route here automatically:
* 1. Registers it on ipcMain (in main.ts via `registerIPC`)
* 2. Exposes it on window.api (in preload.ts via `buildPreloadAPI`)
* 3. Types window.api (via the InferAPI utility below)
* 2. Exposes it on jrc.api (in preload.ts via `buildPreloadAPI`)
* 3. Types jrc.api (via the InferAPI utility below)
*
* Route shapes:
* invoke → renderer calls window.api.foo(...args) → Promise<T>
* send → renderer calls window.api.foo(...args) → void (fire & forget)
* on → renderer calls window.api.onFoo(cb) → unsubscribe fn
* invoke → renderer calls jrc.api.foo(...args) → Promise<T>
* send → renderer calls jrc.api.foo(...args) → void (fire & forget)
* on → renderer calls jrc.api.onFoo(cb) → unsubscribe fn
* main pushes via webContents.send(channel, ...args)
*/

Expand All @@ -33,7 +33,7 @@ type SendRoute = {
type OnRoute = {
type: 'on';
channel: string;
/** Cast a function signature here to type the callback args on window.api.onFoo.
/** Cast a function signature here to type the callback args on jrc.api.onFoo.
* e.g. `args: {} as (profileId: string, line: ConsoleLine) => void`
* Never called at runtime — purely a compile-time phantom. */
args?: (...args: any[]) => void;
Expand All @@ -43,7 +43,7 @@ type Route = InvokeRoute | SendRoute | OnRoute;

export type RouteMap = Record<string, Route>;

// ─── Type inference: RouteMap → window.api shape ──────────────────────────────
// ─── Type inference: RouteMap → jrc.api shape ─────────────────────────────────

type InvokeAPI<R extends InvokeRoute> = R['handler'] extends (
_e: any,
Expand All @@ -60,7 +60,7 @@ type OnAPI<K extends string, R extends OnRoute> = R extends { args: (...args: in
? { [key in `on${Capitalize<K>}`]: (cb: (...args: A) => void) => () => void }
: { [key in `on${Capitalize<K>}`]: (cb: (...args: any[]) => void) => () => void };

/** Derives the full window.api type from a RouteMap. */
/** Derives the full jrc.api type from a RouteMap. */
export type InferAPI<M extends RouteMap> = {
[K in keyof M as M[K]['type'] extends 'on' ? never : K]: M[K] extends InvokeRoute
? InvokeAPI<M[K]>
Expand Down Expand Up @@ -89,7 +89,7 @@ export function registerIPC(routes: RouteMap[]): void {
}
}

// ─── Preload: build the window.api object ────────────────────────────────────
// ─── Preload: build the jrc.api object ───────────────────────────────────────

export function buildPreloadAPI(routes: RouteMap[]): Record<string, unknown> {
const api: Record<string, unknown> = {};
Expand Down
6 changes: 3 additions & 3 deletions src/main/core/JRCEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ let env: JRCEnvironment = {
isReady: false,
devMode: null as unknown as JRCEnvironment['devMode'],
type: null as unknown as JRCEnvironment['type'],
startUpSource: null as unknown as JRCEnvironment['startUpSource'],
launchContext: null as unknown as JRCEnvironment['launchContext'],
};

export function loadEnvironment() {
env = {
isReady: true,
devMode: getSettings().devModeEnabled,
type: app.isPackaged ? 'prod' : 'dev',
startUpSource: detectStartupSource(),
launchContext: detectLaunchContext(),
};

broadcast();
Expand All @@ -33,7 +33,7 @@ function broadcast(channel: string = EnvironmentIPC.change.channel) {
BrowserWindow.getAllWindows().forEach((w) => w.webContents.send(channel, env));
}

function detectStartupSource(): JRCEnvironment['startUpSource'] {
function detectLaunchContext(): JRCEnvironment['launchContext'] {
if (!app.isPackaged) return 'development';

const login = app.getLoginItemSettings();
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/RestAPI.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import http from 'http';
import { routes } from '../api/_index';
import { REST_API_CONFIG } from '../shared/config/API.config';
import { CompiledRoute, Params } from '../shared/types/RestAPI.types';
import { CompiledRoute, Params } from '../shared/types/API.types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

Expand Down
3 changes: 1 addition & 2 deletions src/main/core/Store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { app } from 'electron';
import Store from 'electron-store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { AppSettings } from '../shared/config/Settings.config';
import { AppSettings, DEFAULT_SETTINGS } from '../shared/config/Settings.config';
import { Profile } from '../shared/types/Profile.types';

interface StoreSchema {
Expand Down
2 changes: 1 addition & 1 deletion src/main/core/WindowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function createWindow(onClose: (e: Electron.Event) => void): BrowserWindo
backgroundColor: (ALL_THEMES.find((t) => t.id === getSettings().themeId) ?? BUILTIN_THEME)
.colors['base-950'],
icon: getIconImage(),
show: getEnvironment().startUpSource !== 'withSystem',
show: getEnvironment().launchContext !== 'withSystem',
webPreferences: {
preload: path.join(__dirname, '../preload.js'),
contextIsolation: true,
Expand Down
18 changes: 14 additions & 4 deletions src/main/core/process/ProcessManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BrowserWindow } from 'electron';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { ProcessIPC } from '../../ipc/Process.ipc';
import { DEFAULT_JAR_RESOLUTION } from '../../shared/config/JarResolution.config';
import { PROTECTED_PROCESS_NAMES } from '../../shared/config/Scanner.config';
import {
ConsoleLine,
Expand All @@ -17,10 +16,15 @@ import { startLogSession, stopLogSession, writeLogLine } from './FileLogger';
import { gracefulStop } from './GracefulStop';

import fs from 'fs';
import { patternToRegex } from '../../shared/config/JarResolution.config';

const SELF_PROCESS_NAME = 'Java Client Runner';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

type SystemMessageType =
| 'start'
| 'stopping'
Expand Down Expand Up @@ -61,7 +65,13 @@ function compareVersionArrays(a: number[], b: number[]): number {
}

function resolveJarPath(profile: Profile): { jarPath: string; error?: string } {
const res = profile.jarResolution ?? DEFAULT_JAR_RESOLUTION;
const res = profile.jarResolution ?? {
enabled: false,
baseDir: '',
pattern: 'app-{version}.jar',
strategy: 'highest-version' as const,
regexOverride: '',
};

if (!res.enabled) {
return { jarPath: profile.jarPath };
Expand Down
26 changes: 25 additions & 1 deletion src/main/ipc/Dev.ipc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BrowserWindow } from 'electron';
import { app, BrowserWindow, shell } from 'electron';
import { readFileSync } from 'fs';
import { join } from 'path';
import type { RouteMap } from '../core/IPCController';
import { getAllProfiles } from '../core/Store';
import { DEFAULT_SETTINGS } from '../shared/config/Settings.config';
Expand Down Expand Up @@ -37,4 +39,26 @@ export const DevIPC = {
store.set('settings', DEFAULT_SETTINGS);
},
},

getStoreJson: {
type: 'invoke',
channel: 'dev:getStoreJson',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
try {
return readFileSync(storePath, 'utf-8');
} catch {
return '{}';
}
},
},

openStoreFile: {
type: 'invoke',
channel: 'dev:openStoreFile',
handler: () => {
const storePath = join(app.getPath('userData'), 'java-runner-config.json');
shell.showItemInFolder(storePath);
},
},
} satisfies RouteMap;
10 changes: 8 additions & 2 deletions src/main/ipc/JarResolution.ipc.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import fs from 'fs';
import path from 'path';
import type { RouteMap } from '../core/IPCController';
import { patternToRegex } from '../shared/config/JarResolution.config';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/JarResolution.types';
import type { JarResolutionConfig, JarResolutionResult } from '../shared/types/Profile.types';

function patternToRegex(pattern: string): RegExp {
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, (c) =>
c === '{' || c === '}' ? c : `\\${c}`
);
return new RegExp(`^${escaped.replace(/\{version\}/g, '(.+)')}$`, 'i');
}

function parseVersion(str: string): number[] {
return str
Expand Down
3 changes: 2 additions & 1 deletion src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
app.quit();
} else {
app.on('second-instance', () => {
app.on('second-instance', (_event, argv) => {
if (argv.includes('--autostart')) return;
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.show();
Expand Down
5 changes: 3 additions & 2 deletions src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { buildPreloadAPI } from './core/IPCController';
import { allRoutes } from './ipc/_index';
import { EnvironmentIPC } from './ipc/Environment.ipc';

contextBridge.exposeInMainWorld('api', buildPreloadAPI([...allRoutes]));
const api = buildPreloadAPI([...allRoutes]);
const env = buildPreloadAPI([EnvironmentIPC]);

contextBridge.exposeInMainWorld('env', buildPreloadAPI([EnvironmentIPC]));
contextBridge.exposeInMainWorld('jrc', { api, env });
Loading
Loading