@kaluchi/jdtbridge 1.1.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/README.md +102 -0
- package/bin/jdt.mjs +3 -0
- package/package.json +44 -0
- package/src/args.mjs +37 -0
- package/src/cli.mjs +174 -0
- package/src/client.mjs +168 -0
- package/src/color.mjs +40 -0
- package/src/commands/build.mjs +40 -0
- package/src/commands/editor.mjs +49 -0
- package/src/commands/errors.mjs +52 -0
- package/src/commands/find.mjs +41 -0
- package/src/commands/hierarchy.mjs +48 -0
- package/src/commands/implementors.mjs +34 -0
- package/src/commands/project-info.mjs +45 -0
- package/src/commands/projects.mjs +16 -0
- package/src/commands/refactoring.mjs +116 -0
- package/src/commands/references.mjs +51 -0
- package/src/commands/setup.mjs +383 -0
- package/src/commands/source.mjs +43 -0
- package/src/commands/subtypes.mjs +33 -0
- package/src/commands/test.mjs +47 -0
- package/src/commands/type-info.mjs +47 -0
- package/src/discovery.mjs +82 -0
- package/src/eclipse.mjs +324 -0
- package/src/format/project-info.mjs +118 -0
- package/src/format/references.mjs +28 -0
- package/src/format/test-results.mjs +31 -0
- package/src/home.mjs +55 -0
- package/src/paths.mjs +17 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// Setup command — install/update/remove the Eclipse JDT Bridge plugin.
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join, resolve, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { readConfig, writeConfig } from "../home.mjs";
|
|
9
|
+
import { discoverInstances } from "../discovery.mjs";
|
|
10
|
+
import { green, red, bold, dim } from "../color.mjs";
|
|
11
|
+
import { parseFlags } from "../args.mjs";
|
|
12
|
+
import {
|
|
13
|
+
eclipseExe,
|
|
14
|
+
isEclipseRunning,
|
|
15
|
+
findEclipsePath,
|
|
16
|
+
getEclipseVersion,
|
|
17
|
+
detectProfile,
|
|
18
|
+
getInstalledVersion,
|
|
19
|
+
stopEclipse,
|
|
20
|
+
startEclipse,
|
|
21
|
+
getEclipseJavaHome,
|
|
22
|
+
generateTargetPlatform,
|
|
23
|
+
isEclipseInstall,
|
|
24
|
+
waitForBridge,
|
|
25
|
+
p2Install,
|
|
26
|
+
p2Uninstall,
|
|
27
|
+
} from "../eclipse.mjs";
|
|
28
|
+
|
|
29
|
+
const BUNDLE_ID = "io.github.kaluchi.jdtbridge";
|
|
30
|
+
const FEATURE_IU = `${BUNDLE_ID}.feature.feature.group`;
|
|
31
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
|
|
33
|
+
// ---- UI helpers ----
|
|
34
|
+
|
|
35
|
+
function ask(question, defaultVal = "") {
|
|
36
|
+
if (!process.stdin.isTTY) return Promise.resolve(defaultVal);
|
|
37
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
38
|
+
return new Promise((res) => {
|
|
39
|
+
rl.question(question, (answer) => {
|
|
40
|
+
rl.close();
|
|
41
|
+
res(answer.trim() || defaultVal);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ok(msg) { console.log(` ${green("\u2713")} ${msg}`); }
|
|
47
|
+
function fail(msg) { console.log(` ${red("\u2717")} ${msg}`); }
|
|
48
|
+
function info(msg) { console.log(` ${dim("\u00b7")} ${msg}`); }
|
|
49
|
+
|
|
50
|
+
// ---- prerequisite checks ----
|
|
51
|
+
|
|
52
|
+
function checkNode() {
|
|
53
|
+
const major = parseInt(process.versions.node);
|
|
54
|
+
return { ok: major >= 20, label: `Node.js ${process.versions.node}` };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function checkJava() {
|
|
58
|
+
try {
|
|
59
|
+
const out = execSync("java -version 2>&1", { encoding: "utf8" });
|
|
60
|
+
const m = out.match(/version "([^"]+)"/);
|
|
61
|
+
return { ok: true, label: `Java ${m ? m[1] : "?"}` };
|
|
62
|
+
} catch {
|
|
63
|
+
return { ok: false, label: "Java \u2014 not found" };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkMaven() {
|
|
68
|
+
try {
|
|
69
|
+
const out = execSync("mvn --version", {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
72
|
+
});
|
|
73
|
+
const m = out.match(/Apache Maven (\S+)/);
|
|
74
|
+
return { ok: true, label: `Maven ${m ? m[1] : "?"}` };
|
|
75
|
+
} catch {
|
|
76
|
+
return { ok: false, label: "Maven \u2014 not found" };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function showPrereqs() {
|
|
81
|
+
console.log(bold("Prerequisites"));
|
|
82
|
+
const checks = [checkNode(), checkJava(), checkMaven()];
|
|
83
|
+
for (const c of checks) (c.ok ? ok : fail)(c.label);
|
|
84
|
+
return checks.every((c) => c.ok);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---- plugin source ----
|
|
88
|
+
|
|
89
|
+
function findRepoRoot() {
|
|
90
|
+
// cli/src/commands/setup.mjs -> 3 levels up = repo root
|
|
91
|
+
const candidate = resolve(__dirname, "..", "..", "..");
|
|
92
|
+
if (
|
|
93
|
+
existsSync(join(candidate, "pom.xml")) &&
|
|
94
|
+
existsSync(join(candidate, "plugin"))
|
|
95
|
+
) {
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getBuiltRepoPath(repoRoot) {
|
|
102
|
+
const dir = join(repoRoot, "site", "target", "repository");
|
|
103
|
+
return existsSync(dir) ? dir : null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---- ensure Eclipse is stopped, with confirmation ----
|
|
107
|
+
|
|
108
|
+
async function ensureStopped() {
|
|
109
|
+
if (!isEclipseRunning()) return true;
|
|
110
|
+
const answer = await ask(" Eclipse is running. Stop it? [Y/n] ", "y");
|
|
111
|
+
if (answer.toLowerCase() === "n") {
|
|
112
|
+
console.log(" Eclipse must be stopped. Aborting.");
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
info("Stopping Eclipse...");
|
|
116
|
+
if (!stopEclipse()) {
|
|
117
|
+
console.error(" Failed to stop Eclipse.");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
info("Eclipse stopped.");
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---- modes ----
|
|
125
|
+
|
|
126
|
+
async function runCheck(config) {
|
|
127
|
+
console.log();
|
|
128
|
+
showPrereqs();
|
|
129
|
+
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(bold("Eclipse"));
|
|
132
|
+
const eclipsePath = findEclipsePath(config);
|
|
133
|
+
if (eclipsePath) {
|
|
134
|
+
const ver = getEclipseVersion(eclipsePath);
|
|
135
|
+
ok(
|
|
136
|
+
`${eclipsePath}${ver ? ` (${ver})` : ""}${config.eclipse ? dim(" \u2014 config") : ""}`,
|
|
137
|
+
);
|
|
138
|
+
const profile = detectProfile(eclipsePath);
|
|
139
|
+
(profile ? ok : fail)(`Profile: ${profile || "not found"}`);
|
|
140
|
+
const running = isEclipseRunning();
|
|
141
|
+
(running ? ok : info)(running ? "Running" : "Not running");
|
|
142
|
+
} else {
|
|
143
|
+
fail("Eclipse not found");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(bold("Bridge"));
|
|
148
|
+
const instances = discoverInstances();
|
|
149
|
+
if (instances.length > 0) {
|
|
150
|
+
for (const inst of instances) {
|
|
151
|
+
ok(`port ${inst.port}, PID ${inst.pid}, workspace ${inst.workspace}`);
|
|
152
|
+
if (inst.version) ok(`Plugin: ${inst.version}${inst.location ? dim(` — ${inst.location}`) : ""}`);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
fail("No live instances");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(bold("Plugin source"));
|
|
160
|
+
const repoRoot = findRepoRoot();
|
|
161
|
+
if (repoRoot) {
|
|
162
|
+
ok(`Repo: ${repoRoot}`);
|
|
163
|
+
const built = getBuiltRepoPath(repoRoot);
|
|
164
|
+
(built ? ok : info)(built ? "p2 site: built" : "Not built yet");
|
|
165
|
+
} else {
|
|
166
|
+
info("Repo not found (CLI not in cloned repo)");
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function runInstall(config, flags) {
|
|
172
|
+
console.log();
|
|
173
|
+
if (!showPrereqs()) {
|
|
174
|
+
console.error("\nMissing prerequisites.");
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Eclipse
|
|
179
|
+
console.log();
|
|
180
|
+
console.log(bold("Eclipse"));
|
|
181
|
+
let eclipsePath = findEclipsePath(config);
|
|
182
|
+
if (!eclipsePath) {
|
|
183
|
+
eclipsePath = await ask(" Eclipse not found. Path: ");
|
|
184
|
+
if (!eclipsePath || !isEclipseInstall(eclipsePath)) {
|
|
185
|
+
console.error(" Not a valid Eclipse installation.");
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const ver = getEclipseVersion(eclipsePath);
|
|
190
|
+
ok(`${eclipsePath}${ver ? ` (${ver})` : ""}`);
|
|
191
|
+
if (config.eclipse !== eclipsePath) {
|
|
192
|
+
writeConfig({ eclipse: eclipsePath });
|
|
193
|
+
info("Saved to config");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const profile = detectProfile(eclipsePath);
|
|
197
|
+
if (!profile) {
|
|
198
|
+
console.error(" Cannot detect p2 profile.");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
ok(`Profile: ${profile}`);
|
|
202
|
+
|
|
203
|
+
const installedVersion = getInstalledVersion(eclipsePath, BUNDLE_ID);
|
|
204
|
+
if (installedVersion) {
|
|
205
|
+
info(`Currently installed: ${installedVersion}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Build
|
|
209
|
+
console.log();
|
|
210
|
+
console.log(bold("Building plugin..."));
|
|
211
|
+
let repoRoot = findRepoRoot();
|
|
212
|
+
if (!repoRoot) {
|
|
213
|
+
repoRoot = config.pluginSource;
|
|
214
|
+
if (!repoRoot || !existsSync(repoRoot)) {
|
|
215
|
+
repoRoot = await ask(" Plugin source repo path: ");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!repoRoot || !existsSync(join(repoRoot, "pom.xml"))) {
|
|
219
|
+
console.error(" Invalid plugin source path.");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
info(`Source: ${repoRoot}`);
|
|
223
|
+
|
|
224
|
+
if (!flags["skip-build"]) {
|
|
225
|
+
generateTargetPlatform(repoRoot, eclipsePath);
|
|
226
|
+
info(`Target platform: ${eclipsePath}`);
|
|
227
|
+
const javaHome = getEclipseJavaHome(eclipsePath);
|
|
228
|
+
const mvnEnv = javaHome
|
|
229
|
+
? { ...process.env, JAVA_HOME: javaHome }
|
|
230
|
+
: process.env;
|
|
231
|
+
if (javaHome) info(`JAVA_HOME: ${javaHome}`);
|
|
232
|
+
const mvnCmd = flags.clean ? "mvn clean verify" : "mvn verify";
|
|
233
|
+
try {
|
|
234
|
+
execSync(mvnCmd, { cwd: repoRoot, stdio: "inherit", timeout: 300_000, env: mvnEnv });
|
|
235
|
+
} catch {
|
|
236
|
+
console.error("\n Build failed.");
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
info("Build skipped (--skip-build)");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const repoDir = getBuiltRepoPath(repoRoot);
|
|
244
|
+
if (!repoDir) {
|
|
245
|
+
console.error(" p2 site not found in site/target/repository/.");
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
ok("Plugin built");
|
|
249
|
+
|
|
250
|
+
// Capture workspace BEFORE stopping Eclipse (instances are filtered by
|
|
251
|
+
// isPidAlive, so after stop they disappear from discoverInstances).
|
|
252
|
+
const wasRunning = isEclipseRunning();
|
|
253
|
+
let workspace = null;
|
|
254
|
+
if (wasRunning) {
|
|
255
|
+
const instances = discoverInstances();
|
|
256
|
+
if (instances.length > 0) workspace = instances[0].workspace;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Install
|
|
260
|
+
console.log();
|
|
261
|
+
console.log(bold("Installing plugin..."));
|
|
262
|
+
if (!(await ensureStopped())) return;
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
if (installedVersion) {
|
|
266
|
+
info("Removing old version...");
|
|
267
|
+
try {
|
|
268
|
+
p2Uninstall(eclipsePath, profile, FEATURE_IU);
|
|
269
|
+
} catch {
|
|
270
|
+
/* may fail on dirty profile -- proceed anyway */
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
info("Installing via p2 director...");
|
|
274
|
+
p2Install(eclipsePath, profile, repoDir, FEATURE_IU);
|
|
275
|
+
ok("Installed");
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.error(`\n ${e.message}`);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const newVersion = getInstalledVersion(eclipsePath, BUNDLE_ID);
|
|
282
|
+
if (newVersion) ok(`Version: ${newVersion}`);
|
|
283
|
+
|
|
284
|
+
// Restart
|
|
285
|
+
console.log();
|
|
286
|
+
const launcherPath = join(eclipsePath, eclipseExe("eclipse"));
|
|
287
|
+
if (existsSync(launcherPath)) {
|
|
288
|
+
if (wasRunning) {
|
|
289
|
+
const pid = startEclipse(eclipsePath, workspace);
|
|
290
|
+
info(`Eclipse started (PID ${pid})`);
|
|
291
|
+
info("Waiting for bridge...");
|
|
292
|
+
try {
|
|
293
|
+
const { port, projects } = await waitForBridge(discoverInstances, pid);
|
|
294
|
+
ok(`Bridge ready on port ${port} (${projects.length} projects)`);
|
|
295
|
+
} catch {
|
|
296
|
+
fail("Bridge did not start (Eclipse may still be loading)");
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
info("Start Eclipse to activate the plugin.");
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
info("Plugin installed. Run your Eclipse product to complete setup and activate the bridge.");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
console.log();
|
|
306
|
+
ok(bold("Setup complete"));
|
|
307
|
+
console.log();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function runRemove(config) {
|
|
311
|
+
console.log();
|
|
312
|
+
console.log(bold("Removing plugin..."));
|
|
313
|
+
|
|
314
|
+
const eclipsePath = findEclipsePath(config);
|
|
315
|
+
if (!eclipsePath) {
|
|
316
|
+
console.error(" Eclipse not found. Run jdt setup --check first.");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const profile = detectProfile(eclipsePath);
|
|
321
|
+
if (!profile) {
|
|
322
|
+
console.error(" Cannot detect p2 profile.");
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const installed = getInstalledVersion(eclipsePath, BUNDLE_ID);
|
|
327
|
+
if (!installed) {
|
|
328
|
+
info("Plugin is not installed.");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
info(`Installed: ${installed}`);
|
|
332
|
+
|
|
333
|
+
if (!(await ensureStopped())) return;
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
p2Uninstall(eclipsePath, profile, FEATURE_IU);
|
|
337
|
+
ok("Plugin removed");
|
|
338
|
+
} catch (e) {
|
|
339
|
+
console.error(` ${e.message}`);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
console.log();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---- entry point ----
|
|
346
|
+
|
|
347
|
+
export async function setup(args) {
|
|
348
|
+
const flags = parseFlags(args);
|
|
349
|
+
const config = readConfig();
|
|
350
|
+
if (flags.eclipse) config.eclipse = flags.eclipse;
|
|
351
|
+
|
|
352
|
+
if (args.includes("--check")) {
|
|
353
|
+
await runCheck(config);
|
|
354
|
+
} else if (args.includes("--remove")) {
|
|
355
|
+
await runRemove(config);
|
|
356
|
+
} else {
|
|
357
|
+
await runInstall(config, flags);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const help = `Set up the Eclipse JDT Bridge plugin.
|
|
362
|
+
|
|
363
|
+
Usage: jdt setup [options]
|
|
364
|
+
|
|
365
|
+
Modes:
|
|
366
|
+
(default) build plugin from source, install into Eclipse
|
|
367
|
+
--check show status of all components (diagnostic only)
|
|
368
|
+
--remove uninstall the plugin from Eclipse
|
|
369
|
+
|
|
370
|
+
Options:
|
|
371
|
+
--eclipse <path> Eclipse installation directory
|
|
372
|
+
--skip-build install last build without rebuilding
|
|
373
|
+
--clean clean build (mvn clean verify)
|
|
374
|
+
|
|
375
|
+
Eclipse must be stopped for install/update/remove operations.
|
|
376
|
+
If Eclipse is running, you will be prompted to stop it.
|
|
377
|
+
|
|
378
|
+
Examples:
|
|
379
|
+
jdt setup
|
|
380
|
+
jdt setup --check
|
|
381
|
+
jdt setup --eclipse D:/eclipse
|
|
382
|
+
jdt setup --skip-build
|
|
383
|
+
jdt setup --remove`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getRaw } from "../client.mjs";
|
|
2
|
+
import { extractPositional, parseFlags } from "../args.mjs";
|
|
3
|
+
import { stripProject } from "../paths.mjs";
|
|
4
|
+
|
|
5
|
+
export async function source(args) {
|
|
6
|
+
const pos = extractPositional(args);
|
|
7
|
+
const flags = parseFlags(args);
|
|
8
|
+
const [fqn, method] = pos;
|
|
9
|
+
if (!fqn) {
|
|
10
|
+
console.error("Usage: source <FQN> [method] [--arity n]");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
let url = `/source?class=${encodeURIComponent(fqn)}`;
|
|
14
|
+
if (method) url += `&method=${encodeURIComponent(method)}`;
|
|
15
|
+
if (flags.arity !== undefined && flags.arity !== true)
|
|
16
|
+
url += `&arity=${flags.arity}`;
|
|
17
|
+
const result = await getRaw(url, 30_000);
|
|
18
|
+
const file = result.headers["x-file"] || "?";
|
|
19
|
+
const startLine = result.headers["x-start-line"] || "?";
|
|
20
|
+
const endLine = result.headers["x-end-line"] || "?";
|
|
21
|
+
|
|
22
|
+
if (startLine === "-1") {
|
|
23
|
+
// Multiple overloads: body has :startLine-endLine prefixes per block
|
|
24
|
+
console.log(
|
|
25
|
+
result.body.replace(
|
|
26
|
+
/^:(\d+-\d+)/gm,
|
|
27
|
+
`${stripProject(file)}:$1`,
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
} else {
|
|
31
|
+
console.log(`${stripProject(file)}:${startLine}-${endLine}`);
|
|
32
|
+
console.log(result.body);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const help = `Print source code of a type or method.
|
|
37
|
+
|
|
38
|
+
Usage: jdt source <FQN> [method] [--arity <n>]
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
jdt source app.m8.dao.StaffDaoImpl
|
|
42
|
+
jdt source app.m8.dao.StaffDaoImpl getStaff
|
|
43
|
+
jdt source app.m8.dao.StaffDaoImpl save --arity 2`;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { get } from "../client.mjs";
|
|
2
|
+
import { extractPositional } from "../args.mjs";
|
|
3
|
+
import { stripProject } from "../paths.mjs";
|
|
4
|
+
|
|
5
|
+
export async function subtypes(args) {
|
|
6
|
+
const pos = extractPositional(args);
|
|
7
|
+
const fqn = pos[0];
|
|
8
|
+
if (!fqn) {
|
|
9
|
+
console.error("Usage: subtypes <FQN>");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const results = await get(
|
|
13
|
+
`/subtypes?class=${encodeURIComponent(fqn)}`,
|
|
14
|
+
30_000,
|
|
15
|
+
);
|
|
16
|
+
if (results.error) {
|
|
17
|
+
console.error(results.error);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (results.length === 0) {
|
|
21
|
+
console.log("(no subtypes)");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const r of results) {
|
|
25
|
+
console.log(`${r.fqn} ${stripProject(r.file)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const help = `Find all direct and indirect subtypes/implementors of a type.
|
|
30
|
+
|
|
31
|
+
Usage: jdt subtypes <FQN>
|
|
32
|
+
|
|
33
|
+
Example: jdt subtypes app.m8.web.shared.core.HasId`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { get } from "../client.mjs";
|
|
2
|
+
import { extractPositional, parseFlags } from "../args.mjs";
|
|
3
|
+
import { formatTestResults } from "../format/test-results.mjs";
|
|
4
|
+
|
|
5
|
+
export async function test(args) {
|
|
6
|
+
const pos = extractPositional(args);
|
|
7
|
+
const flags = parseFlags(args);
|
|
8
|
+
let url = "/test?";
|
|
9
|
+
const fqn = pos[0];
|
|
10
|
+
if (fqn) {
|
|
11
|
+
url += `class=${encodeURIComponent(fqn)}`;
|
|
12
|
+
const method = pos[1];
|
|
13
|
+
if (method) url += `&method=${encodeURIComponent(method)}`;
|
|
14
|
+
} else if (flags.project) {
|
|
15
|
+
url += `project=${encodeURIComponent(flags.project)}`;
|
|
16
|
+
if (flags.package)
|
|
17
|
+
url += `&package=${encodeURIComponent(flags.package)}`;
|
|
18
|
+
} else {
|
|
19
|
+
console.error(
|
|
20
|
+
"Usage: test <FQN> [method] | test --project <name> [--package <pkg>]",
|
|
21
|
+
);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
if (flags.timeout) url += `&timeout=${flags.timeout}`;
|
|
25
|
+
if (args.includes("--no-refresh")) url += "&no-refresh";
|
|
26
|
+
const result = await get(url, 300_000);
|
|
27
|
+
if (result.error) {
|
|
28
|
+
console.error(result.error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
formatTestResults(result);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const help = `Run JUnit tests via Eclipse's built-in test runner.
|
|
35
|
+
|
|
36
|
+
Usage: jdt test <FQN> [method]
|
|
37
|
+
jdt test --project <name> [--package <pkg>]
|
|
38
|
+
|
|
39
|
+
Flags:
|
|
40
|
+
--project <name> run all tests in a project
|
|
41
|
+
--package <pkg> narrow to a specific package (with --project)
|
|
42
|
+
--timeout <sec> test run timeout in seconds (default: 120)
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
jdt test app.m8ws.utils.ObjectMapperTest
|
|
46
|
+
jdt test app.m8ws.utils.ObjectMapperTest testSerialize
|
|
47
|
+
jdt test --project m8-server`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { get } from "../client.mjs";
|
|
2
|
+
import { extractPositional } from "../args.mjs";
|
|
3
|
+
import { stripProject } from "../paths.mjs";
|
|
4
|
+
import { dim } from "../color.mjs";
|
|
5
|
+
|
|
6
|
+
export async function typeInfo(args) {
|
|
7
|
+
const pos = extractPositional(args);
|
|
8
|
+
const fqn = pos[0];
|
|
9
|
+
if (!fqn) {
|
|
10
|
+
console.error("Usage: type-info <FQN>");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const result = await get(
|
|
14
|
+
`/type-info?class=${encodeURIComponent(fqn)}`,
|
|
15
|
+
30_000,
|
|
16
|
+
);
|
|
17
|
+
if (result.error) {
|
|
18
|
+
console.error(result.error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const filePath = stripProject(result.file);
|
|
23
|
+
console.log(`${result.kind} ${result.fqn} ${dim(`(${filePath})`)}`);
|
|
24
|
+
if (result.superclass) console.log(` extends ${result.superclass}`);
|
|
25
|
+
for (const iface of result.interfaces) {
|
|
26
|
+
console.log(` implements ${iface}`);
|
|
27
|
+
}
|
|
28
|
+
if (result.fields.length > 0) {
|
|
29
|
+
console.log();
|
|
30
|
+
for (const f of result.fields) {
|
|
31
|
+
const mods = f.modifiers ? f.modifiers + " " : "";
|
|
32
|
+
console.log(` ${mods}${f.type} ${f.name} ${dim(`:${f.line}`)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (result.methods.length > 0) {
|
|
36
|
+
console.log();
|
|
37
|
+
for (const m of result.methods) {
|
|
38
|
+
console.log(` ${m.signature} ${dim(`:${m.line}`)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const help = `Show class overview: fields, methods, modifiers, and line numbers.
|
|
44
|
+
|
|
45
|
+
Usage: jdt type-info <FQN>
|
|
46
|
+
|
|
47
|
+
Example: jdt type-info app.m8.dto.web.core.IdOrgRoot`;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Eclipse instance discovery — find running instances via bridge files and process list.
|
|
2
|
+
|
|
3
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { instancesDir } from "./home.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} Instance
|
|
9
|
+
* @property {number} port
|
|
10
|
+
* @property {string} token
|
|
11
|
+
* @property {number} pid
|
|
12
|
+
* @property {string} workspace
|
|
13
|
+
* @property {string} [version] - plugin version (added in 1.1)
|
|
14
|
+
* @property {string} [location] - bundle location URI
|
|
15
|
+
* @property {string} file - path to the instance file
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read all instance files from JDTBRIDGE_HOME/instances/.
|
|
20
|
+
* Filters out stale instances (PID not alive).
|
|
21
|
+
* @returns {Instance[]}
|
|
22
|
+
*/
|
|
23
|
+
export function discoverInstances() {
|
|
24
|
+
const dir = instancesDir();
|
|
25
|
+
let files;
|
|
26
|
+
try {
|
|
27
|
+
files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
28
|
+
} catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const instances = [];
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const filePath = join(dir, file);
|
|
35
|
+
try {
|
|
36
|
+
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
37
|
+
if (!data.port || !data.pid) continue;
|
|
38
|
+
if (!isPidAlive(data.pid)) continue;
|
|
39
|
+
instances.push({ ...data, file: filePath });
|
|
40
|
+
} catch {
|
|
41
|
+
// corrupt file — skip
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return instances;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Find a single instance. If multiple are running, prefer one matching
|
|
49
|
+
* the given workspace hint (cwd or explicit). If none match, return the
|
|
50
|
+
* first one found (or null).
|
|
51
|
+
* @param {string} [workspaceHint] - substring to match against workspace path
|
|
52
|
+
* @returns {Instance|null}
|
|
53
|
+
*/
|
|
54
|
+
export function findInstance(workspaceHint) {
|
|
55
|
+
const instances = discoverInstances();
|
|
56
|
+
if (instances.length === 0) return null;
|
|
57
|
+
if (instances.length === 1) return instances[0];
|
|
58
|
+
|
|
59
|
+
if (workspaceHint) {
|
|
60
|
+
const normalized = workspaceHint.replace(/\\/g, "/").toLowerCase();
|
|
61
|
+
const match = instances.find((i) =>
|
|
62
|
+
i.workspace.replace(/\\/g, "/").toLowerCase().includes(normalized),
|
|
63
|
+
);
|
|
64
|
+
if (match) return match;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Default to first
|
|
68
|
+
return instances[0];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a PID is still alive.
|
|
73
|
+
* Sends signal 0 which checks existence without killing.
|
|
74
|
+
*/
|
|
75
|
+
export function isPidAlive(pid) {
|
|
76
|
+
try {
|
|
77
|
+
process.kill(pid, 0);
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|