@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
package/src/eclipse.mjs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// Eclipse management — discovery, lifecycle, p2 operations.
|
|
2
|
+
|
|
3
|
+
import { execSync, spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join, resolve, dirname } from "node:path";
|
|
6
|
+
import { request } from "node:http";
|
|
7
|
+
|
|
8
|
+
const IS_WIN = process.platform === "win32";
|
|
9
|
+
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Return platform-specific executable name. */
|
|
15
|
+
export function eclipseExe(name) {
|
|
16
|
+
return IS_WIN ? name + ".exe" : name;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Check if any Eclipse process is running. */
|
|
20
|
+
export function isEclipseRunning() {
|
|
21
|
+
try {
|
|
22
|
+
if (IS_WIN) {
|
|
23
|
+
const out = execSync("tasklist", {
|
|
24
|
+
encoding: "utf8",
|
|
25
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
26
|
+
});
|
|
27
|
+
return out.toLowerCase().includes("eclipse.exe");
|
|
28
|
+
}
|
|
29
|
+
execSync("pgrep -f eclipse", { stdio: "ignore" });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Find Eclipse installation directory.
|
|
38
|
+
* Checks config first, then well-known locations.
|
|
39
|
+
* Accepts either a traditional install with eclipsec(.exe) or any
|
|
40
|
+
* Eclipse-based product that has a .eclipseproduct marker file
|
|
41
|
+
* (e.g. Spring Tools, STS).
|
|
42
|
+
*/
|
|
43
|
+
export function isEclipseInstall(dir) {
|
|
44
|
+
if (!dir) return false;
|
|
45
|
+
if (existsSync(join(dir, eclipseExe("eclipsec")))) return true;
|
|
46
|
+
if (existsSync(join(dir, ".eclipseproduct"))) return true;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function findEclipsePath(config) {
|
|
51
|
+
if (config.eclipse && isEclipseInstall(config.eclipse)) {
|
|
52
|
+
return config.eclipse;
|
|
53
|
+
}
|
|
54
|
+
const candidates = IS_WIN
|
|
55
|
+
? ["D:/eclipse", "C:/eclipse"]
|
|
56
|
+
: [
|
|
57
|
+
"/usr/local/eclipse",
|
|
58
|
+
"/opt/eclipse",
|
|
59
|
+
`${process.env.HOME}/eclipse`,
|
|
60
|
+
];
|
|
61
|
+
for (const p of candidates) {
|
|
62
|
+
if (isEclipseInstall(p)) return p;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Read Eclipse version from .eclipseproduct file. */
|
|
68
|
+
export function getEclipseVersion(eclipsePath) {
|
|
69
|
+
const f = join(eclipsePath, ".eclipseproduct");
|
|
70
|
+
if (!existsSync(f)) return null;
|
|
71
|
+
const m = readFileSync(f, "utf8").match(/version=(\S+)/);
|
|
72
|
+
return m ? m[1] : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract JAVA_HOME from eclipse.ini (-vm entry).
|
|
77
|
+
* Returns absolute path to JDK/JRE home, or null if not found.
|
|
78
|
+
*/
|
|
79
|
+
export function getEclipseJavaHome(eclipsePath) {
|
|
80
|
+
const iniFile = join(eclipsePath, "eclipse.ini");
|
|
81
|
+
if (!existsSync(iniFile)) return null;
|
|
82
|
+
const lines = readFileSync(iniFile, "utf8").split(/\r?\n/);
|
|
83
|
+
const vmIndex = lines.indexOf("-vm");
|
|
84
|
+
if (vmIndex === -1 || vmIndex + 1 >= lines.length) return null;
|
|
85
|
+
const vmPath = lines[vmIndex + 1].trim();
|
|
86
|
+
// -vm points to a bin/ dir or a javaw.exe — resolve to JRE/JDK home
|
|
87
|
+
const resolved = resolve(eclipsePath, vmPath);
|
|
88
|
+
// e.g. plugins/.../jre/bin → go up to jre (or jdk root)
|
|
89
|
+
if (resolved.endsWith("bin") || resolved.endsWith("bin/") || resolved.endsWith("bin\\")) {
|
|
90
|
+
const parent = dirname(resolved);
|
|
91
|
+
// If parent is "jre", go one more level up for JDK home
|
|
92
|
+
return parent;
|
|
93
|
+
}
|
|
94
|
+
// If it points to an executable, go up two levels (bin/java.exe → jre)
|
|
95
|
+
return dirname(dirname(resolved));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Detect the p2 profile name from the profile registry. */
|
|
99
|
+
export function detectProfile(eclipsePath) {
|
|
100
|
+
const regDir = join(
|
|
101
|
+
eclipsePath,
|
|
102
|
+
"p2",
|
|
103
|
+
"org.eclipse.equinox.p2.engine",
|
|
104
|
+
"profileRegistry",
|
|
105
|
+
);
|
|
106
|
+
if (!existsSync(regDir)) return null;
|
|
107
|
+
const dirs = readdirSync(regDir).filter((d) => d.endsWith(".profile"));
|
|
108
|
+
if (dirs.length === 0) return null;
|
|
109
|
+
const epp = dirs.find((d) => d.startsWith("epp.package."));
|
|
110
|
+
return (epp || dirs[0]).replace(".profile", "");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Find the installed version of a bundle in Eclipse plugins dir. */
|
|
114
|
+
export function getInstalledVersion(eclipsePath, bundleId) {
|
|
115
|
+
const pluginsDir = join(eclipsePath, "plugins");
|
|
116
|
+
if (!existsSync(pluginsDir)) return null;
|
|
117
|
+
const jars = readdirSync(pluginsDir).filter(
|
|
118
|
+
(f) => f.startsWith(bundleId + "_") && f.endsWith(".jar"),
|
|
119
|
+
);
|
|
120
|
+
if (jars.length === 0) return null;
|
|
121
|
+
const m = jars[jars.length - 1].match(/_(.+)\.jar$/);
|
|
122
|
+
return m ? m[1] : "unknown";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Stop Eclipse gracefully, then force-kill if needed.
|
|
127
|
+
* Returns true if Eclipse is stopped, false if it could not be stopped.
|
|
128
|
+
*/
|
|
129
|
+
export function stopEclipse() {
|
|
130
|
+
if (!isEclipseRunning()) return true;
|
|
131
|
+
try {
|
|
132
|
+
if (IS_WIN) {
|
|
133
|
+
execSync("taskkill /IM eclipse.exe", { stdio: "ignore" });
|
|
134
|
+
} else {
|
|
135
|
+
execSync("pkill -f eclipse", { stdio: "ignore" });
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
/* ignore */
|
|
139
|
+
}
|
|
140
|
+
for (let i = 0; i < 60; i++) {
|
|
141
|
+
sleep(500);
|
|
142
|
+
if (!isEclipseRunning()) return true;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
if (IS_WIN) {
|
|
146
|
+
execSync("taskkill /F /IM eclipse.exe", { stdio: "ignore" });
|
|
147
|
+
} else {
|
|
148
|
+
execSync("pkill -9 -f eclipse", { stdio: "ignore" });
|
|
149
|
+
}
|
|
150
|
+
sleep(2000);
|
|
151
|
+
} catch {
|
|
152
|
+
/* ignore */
|
|
153
|
+
}
|
|
154
|
+
return !isEclipseRunning();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Start Eclipse as a detached process.
|
|
159
|
+
* @returns {number} PID of the launched process
|
|
160
|
+
*/
|
|
161
|
+
export function startEclipse(eclipsePath, workspace) {
|
|
162
|
+
const exe = join(eclipsePath, eclipseExe("eclipse"));
|
|
163
|
+
const args = workspace ? ["-data", workspace] : [];
|
|
164
|
+
const child = spawn(exe, args, {
|
|
165
|
+
detached: true,
|
|
166
|
+
stdio: "ignore",
|
|
167
|
+
windowsHide: false,
|
|
168
|
+
});
|
|
169
|
+
child.unref();
|
|
170
|
+
return child.pid;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Generate jdtbridge.target pointing to the Eclipse installation. */
|
|
174
|
+
export function generateTargetPlatform(repoRoot, eclipsePath) {
|
|
175
|
+
const targetFile = join(repoRoot, "jdtbridge.target");
|
|
176
|
+
const content = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
177
|
+
<?pde version="3.8"?>
|
|
178
|
+
<target name="eclipse-local" sequenceNumber="1">
|
|
179
|
+
<locations>
|
|
180
|
+
<location path="${eclipsePath}" type="Directory"/>
|
|
181
|
+
</locations>
|
|
182
|
+
</target>
|
|
183
|
+
`;
|
|
184
|
+
writeFileSync(targetFile, content);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function findLauncherJar(eclipsePath) {
|
|
188
|
+
const pluginsDir = join(eclipsePath, "plugins");
|
|
189
|
+
if (!existsSync(pluginsDir)) return null;
|
|
190
|
+
const jars = readdirSync(pluginsDir).filter((f) =>
|
|
191
|
+
f.startsWith("org.eclipse.equinox.launcher_") && f.endsWith(".jar"),
|
|
192
|
+
);
|
|
193
|
+
if (jars.length === 0) return null;
|
|
194
|
+
jars.sort();
|
|
195
|
+
return join(pluginsDir, jars[jars.length - 1]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Run the p2 director application (headless Eclipse). */
|
|
199
|
+
export function runDirector(eclipsePath, profile, extraArgs) {
|
|
200
|
+
const eclipsecPath = join(eclipsePath, eclipseExe("eclipsec"));
|
|
201
|
+
|
|
202
|
+
let cmd;
|
|
203
|
+
let prefixArgs = [];
|
|
204
|
+
|
|
205
|
+
if (existsSync(eclipsecPath)) {
|
|
206
|
+
// Classic Eclipse launcher
|
|
207
|
+
cmd = `"${eclipsecPath}"`;
|
|
208
|
+
} else {
|
|
209
|
+
// Fallback: run via Equinox launcher JAR (works for Eclipse-based products
|
|
210
|
+
// such as Spring Tools that ship a branded launcher instead of eclipsec).
|
|
211
|
+
const launcherJar = findLauncherJar(eclipsePath);
|
|
212
|
+
if (!launcherJar) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
"Cannot find eclipsec(.exe) or org.eclipse.equinox.launcher_*.jar in Eclipse installation.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
cmd = "java";
|
|
218
|
+
prefixArgs = ["-jar", `"${launcherJar}"`];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const args = [
|
|
222
|
+
cmd,
|
|
223
|
+
...prefixArgs,
|
|
224
|
+
"-nosplash",
|
|
225
|
+
"-application",
|
|
226
|
+
"org.eclipse.equinox.p2.director",
|
|
227
|
+
"-profile",
|
|
228
|
+
profile,
|
|
229
|
+
"-destination",
|
|
230
|
+
`"${eclipsePath}"`,
|
|
231
|
+
...extraArgs,
|
|
232
|
+
].join(" ");
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
return execSync(args + " 2>&1", {
|
|
236
|
+
encoding: "utf8",
|
|
237
|
+
timeout: 180_000,
|
|
238
|
+
});
|
|
239
|
+
} catch (e) {
|
|
240
|
+
const output = e.stdout || e.stderr || e.message;
|
|
241
|
+
const lines = output
|
|
242
|
+
.split("\n")
|
|
243
|
+
.filter(
|
|
244
|
+
(l) =>
|
|
245
|
+
!l.includes("DEBUG") &&
|
|
246
|
+
!l.includes("INFO:") &&
|
|
247
|
+
!l.includes("spifly") &&
|
|
248
|
+
l.trim(),
|
|
249
|
+
);
|
|
250
|
+
throw new Error(lines.join("\n"));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Install a feature via p2 director from a local repository. */
|
|
255
|
+
export function p2Install(eclipsePath, profile, repoPath, featureIU) {
|
|
256
|
+
const repoUrl = `file:///${repoPath.replace(/\\/g, "/")}`;
|
|
257
|
+
return runDirector(eclipsePath, profile, [
|
|
258
|
+
"-repository",
|
|
259
|
+
`"${repoUrl}"`,
|
|
260
|
+
"-installIU",
|
|
261
|
+
featureIU,
|
|
262
|
+
]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Uninstall a feature via p2 director. */
|
|
266
|
+
export function p2Uninstall(eclipsePath, profile, featureIU) {
|
|
267
|
+
return runDirector(eclipsePath, profile, ["-uninstallIU", featureIU]);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Wait for a bridge instance file with the given PID to appear,
|
|
272
|
+
* then hit /projects as a health check.
|
|
273
|
+
* @param {Function} discoverFn - discoverInstances function
|
|
274
|
+
* @param {number} pid - PID of the Eclipse process to wait for
|
|
275
|
+
* @param {number} [timeoutSec=120] - max seconds to wait
|
|
276
|
+
* @returns {Promise<{port: number, projects: string[]}>} port and project list
|
|
277
|
+
*/
|
|
278
|
+
export function waitForBridge(discoverFn, pid, timeoutSec = 120) {
|
|
279
|
+
const deadline = Date.now() + timeoutSec * 1000;
|
|
280
|
+
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
function poll() {
|
|
283
|
+
if (Date.now() > deadline) {
|
|
284
|
+
reject(new Error("Timed out waiting for bridge"));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const instances = discoverFn();
|
|
288
|
+
const inst = instances.find((i) => i.pid === pid);
|
|
289
|
+
if (!inst) {
|
|
290
|
+
setTimeout(poll, 2000);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// Instance found — health check via /projects
|
|
294
|
+
healthCheck(inst.port, inst.token)
|
|
295
|
+
.then((projects) => resolve({ port: inst.port, projects }))
|
|
296
|
+
.catch(() => setTimeout(poll, 2000));
|
|
297
|
+
}
|
|
298
|
+
poll();
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function healthCheck(port, token) {
|
|
303
|
+
return new Promise((resolve, reject) => {
|
|
304
|
+
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
|
305
|
+
const req = request(
|
|
306
|
+
{ hostname: "127.0.0.1", port, path: "/projects", timeout: 5000, headers },
|
|
307
|
+
(res) => {
|
|
308
|
+
let data = "";
|
|
309
|
+
res.on("data", (chunk) => (data += chunk));
|
|
310
|
+
res.on("end", () => {
|
|
311
|
+
if (res.statusCode !== 200) {
|
|
312
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
try { resolve(JSON.parse(data)); }
|
|
316
|
+
catch { reject(new Error("Invalid JSON")); }
|
|
317
|
+
});
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
321
|
+
req.on("error", reject);
|
|
322
|
+
req.end();
|
|
323
|
+
});
|
|
324
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Adaptive project-info formatter.
|
|
2
|
+
// Adjusts detail level (packages → types → methods) to fit --lines budget.
|
|
3
|
+
|
|
4
|
+
export function formatProjectInfo(data, maxLines) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
|
|
7
|
+
// Header (always shown)
|
|
8
|
+
lines.push(data.name);
|
|
9
|
+
lines.push(`Location: ${data.location}`);
|
|
10
|
+
if (data.natures.length) lines.push(`Natures: ${data.natures.join(", ")}`);
|
|
11
|
+
const deps = data.dependencies.length
|
|
12
|
+
? data.dependencies.join(", ")
|
|
13
|
+
: "(none)";
|
|
14
|
+
lines.push(`Dependencies: ${deps}`);
|
|
15
|
+
lines.push(`Total: ${data.totalTypes} types`);
|
|
16
|
+
lines.push("");
|
|
17
|
+
|
|
18
|
+
let budget = maxLines - lines.length;
|
|
19
|
+
if (budget <= 0) return lines.join("\n");
|
|
20
|
+
|
|
21
|
+
// Count lines per detail tier
|
|
22
|
+
const visOrder = ["public", "protected", "default", "private"];
|
|
23
|
+
let pkgLineCount = 0;
|
|
24
|
+
let typeLineCount = 0;
|
|
25
|
+
const visCount = { public: 0, protected: 0, default: 0, private: 0 };
|
|
26
|
+
|
|
27
|
+
for (const root of data.sourceRoots) {
|
|
28
|
+
pkgLineCount++;
|
|
29
|
+
typeLineCount++;
|
|
30
|
+
for (const pkg of root.packages) {
|
|
31
|
+
pkgLineCount++;
|
|
32
|
+
typeLineCount++;
|
|
33
|
+
for (const type of pkg.types) {
|
|
34
|
+
typeLineCount++;
|
|
35
|
+
const m = type.methods;
|
|
36
|
+
if (m && typeof m === "object" && !Array.isArray(m)) {
|
|
37
|
+
for (const vis of visOrder) {
|
|
38
|
+
visCount[vis] += (m[vis] || []).length;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Cumulative: each visibility layer adds on top
|
|
46
|
+
const cumVis = {};
|
|
47
|
+
let cum = typeLineCount;
|
|
48
|
+
for (const vis of visOrder) {
|
|
49
|
+
cum += visCount[vis];
|
|
50
|
+
cumVis[vis] = cum;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Pick richest tier that fits entirely
|
|
54
|
+
let tier = "packages";
|
|
55
|
+
if (data.membersIncluded) {
|
|
56
|
+
for (let i = visOrder.length - 1; i >= 0; i--) {
|
|
57
|
+
if (cumVis[visOrder[i]] <= budget) {
|
|
58
|
+
tier = visOrder[i];
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (tier === "packages" && typeLineCount <= budget) tier = "types";
|
|
63
|
+
} else {
|
|
64
|
+
if (typeLineCount <= budget) tier = "types";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Which visibility groups to show
|
|
68
|
+
const showVis = new Set();
|
|
69
|
+
const tierVisIdx = visOrder.indexOf(tier);
|
|
70
|
+
if (tierVisIdx >= 0) {
|
|
71
|
+
for (let i = 0; i <= tierVisIdx; i++) showVis.add(visOrder[i]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Render
|
|
75
|
+
let renderedPkgs = 0;
|
|
76
|
+
let totalPkgs = 0;
|
|
77
|
+
for (const root of data.sourceRoots) {
|
|
78
|
+
totalPkgs += root.packages.length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const root of data.sourceRoots) {
|
|
82
|
+
lines.push(`${root.path}/ (${root.typeCount} types)`);
|
|
83
|
+
|
|
84
|
+
for (const pkg of root.packages) {
|
|
85
|
+
if (
|
|
86
|
+
tier === "packages" &&
|
|
87
|
+
renderedPkgs >= budget - data.sourceRoots.length
|
|
88
|
+
) {
|
|
89
|
+
lines.push(` ... and ${totalPkgs - renderedPkgs} more packages`);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (tier === "packages") {
|
|
94
|
+
lines.push(` ${pkg.name} (${pkg.types.length})`);
|
|
95
|
+
renderedPkgs++;
|
|
96
|
+
} else {
|
|
97
|
+
lines.push(` ${pkg.name}`);
|
|
98
|
+
for (const type of pkg.types) {
|
|
99
|
+
const kindSuffix = type.kind !== "class" ? ` (${type.kind})` : "";
|
|
100
|
+
const fieldsInfo = showVis.size > 0 ? ` (${type.fields}f)` : "";
|
|
101
|
+
lines.push(` ${type.name}${kindSuffix}${fieldsInfo}`);
|
|
102
|
+
|
|
103
|
+
const m = type.methods;
|
|
104
|
+
if (showVis.size > 0 && m && typeof m === "object" && !Array.isArray(m)) {
|
|
105
|
+
for (const vis of visOrder) {
|
|
106
|
+
if (!showVis.has(vis)) break;
|
|
107
|
+
for (const method of m[vis] || []) {
|
|
108
|
+
lines.push(` ${method}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// References output formatter.
|
|
2
|
+
// Groups source references by file, binary references by project+jar.
|
|
3
|
+
|
|
4
|
+
import { stripProject } from "../paths.mjs";
|
|
5
|
+
|
|
6
|
+
export function formatReferences(results) {
|
|
7
|
+
let lastGroup = "";
|
|
8
|
+
for (const r of results) {
|
|
9
|
+
const f = stripProject(r.file);
|
|
10
|
+
const isBinary = r.line <= 0;
|
|
11
|
+
const group = isBinary ? `${r.project || "?"} ${f}` : f;
|
|
12
|
+
if (group !== lastGroup) {
|
|
13
|
+
if (lastGroup) console.log();
|
|
14
|
+
if (isBinary) {
|
|
15
|
+
console.log(`${r.project || "?"} (${f.split(/[/\\]/).pop()})`);
|
|
16
|
+
}
|
|
17
|
+
lastGroup = group;
|
|
18
|
+
}
|
|
19
|
+
if (isBinary) {
|
|
20
|
+
if (r.in) console.log(` ${r.in}`);
|
|
21
|
+
if (r.content) console.log(` | ${r.content}`);
|
|
22
|
+
} else {
|
|
23
|
+
console.log(`${f}:${r.line}`);
|
|
24
|
+
if (r.in) console.log(` in ${r.in}`);
|
|
25
|
+
if (r.content) console.log(` | ${r.content}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Test results formatter.
|
|
2
|
+
// Shows summary line + failure details with truncated stack traces.
|
|
3
|
+
|
|
4
|
+
import { red, green, yellow, bold } from "../color.mjs";
|
|
5
|
+
|
|
6
|
+
export function formatTestResults(result) {
|
|
7
|
+
// Summary line
|
|
8
|
+
const parts = [`${result.total} tests`];
|
|
9
|
+
if (result.passed > 0) parts.push(green(`${result.passed} passed`));
|
|
10
|
+
if (result.failed > 0) parts.push(red(`${result.failed} failed`));
|
|
11
|
+
if (result.errors > 0) parts.push(red(`${result.errors} errors`));
|
|
12
|
+
if (result.ignored > 0) parts.push(yellow(`${result.ignored} ignored`));
|
|
13
|
+
const time = Number.isFinite(result.time) ? result.time : 0;
|
|
14
|
+
parts.push(`${time.toFixed(1)}s`);
|
|
15
|
+
console.log(parts.join(", "));
|
|
16
|
+
|
|
17
|
+
// Failure details
|
|
18
|
+
if (result.failures && result.failures.length > 0) {
|
|
19
|
+
console.log();
|
|
20
|
+
for (const f of result.failures) {
|
|
21
|
+
const status =
|
|
22
|
+
f.status === "FAILURE" ? red(f.status) : bold(red(f.status));
|
|
23
|
+
console.log(`${status} ${f.class}.${f.method}`);
|
|
24
|
+
if (f.trace) {
|
|
25
|
+
const traceLines = f.trace.split("\n").slice(0, 10);
|
|
26
|
+
for (const line of traceLines) console.log(` ${line}`);
|
|
27
|
+
if (f.trace.split("\n").length > 10) console.log(" ...");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/home.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// JDTBRIDGE_HOME management — config dir, instances, plugin artifacts.
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_HOME = join(homedir(), ".jdtbridge");
|
|
8
|
+
|
|
9
|
+
let _home;
|
|
10
|
+
|
|
11
|
+
/** Returns JDTBRIDGE_HOME path, creating it if needed. */
|
|
12
|
+
export function getHome() {
|
|
13
|
+
if (_home) return _home;
|
|
14
|
+
_home = process.env.JDTBRIDGE_HOME || DEFAULT_HOME;
|
|
15
|
+
ensureDir(_home);
|
|
16
|
+
return _home;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Directory where running Eclipse instances write their bridge files. */
|
|
20
|
+
export function instancesDir() {
|
|
21
|
+
const dir = join(getHome(), "instances");
|
|
22
|
+
ensureDir(dir);
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Read config.json from JDTBRIDGE_HOME. Returns {} if missing. */
|
|
27
|
+
export function readConfig() {
|
|
28
|
+
const configPath = join(getHome(), "config.json");
|
|
29
|
+
if (!existsSync(configPath)) return {};
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(configPath, "utf8"));
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Write config.json to JDTBRIDGE_HOME. Merges with existing. */
|
|
38
|
+
export function writeConfig(updates) {
|
|
39
|
+
const configPath = join(getHome(), "config.json");
|
|
40
|
+
const current = readConfig();
|
|
41
|
+
const merged = { ...current, ...updates };
|
|
42
|
+
writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n");
|
|
43
|
+
return merged;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Reset cached home path (for testing). */
|
|
47
|
+
export function resetHome() {
|
|
48
|
+
_home = undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ensureDir(dir) {
|
|
52
|
+
if (!existsSync(dir)) {
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/paths.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Path utilities for workspace-relative paths.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Strip leading slash from workspace-relative path.
|
|
5
|
+
* Eclipse returns paths like /m8-server/src/... — we want m8-server/src/...
|
|
6
|
+
*/
|
|
7
|
+
export function stripProject(wsPath) {
|
|
8
|
+
return wsPath.startsWith("/") ? wsPath.slice(1) : wsPath;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ensure path starts with / for workspace-relative API calls.
|
|
13
|
+
* Accepts: m8-server/src/... or /m8-server/src/...
|
|
14
|
+
*/
|
|
15
|
+
export function toWsPath(p) {
|
|
16
|
+
return p.startsWith("/") ? p : "/" + p;
|
|
17
|
+
}
|