@groma/scanner-java 0.1.0 → 0.1.1
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/dist/worker.jar +0 -0
- package/package.json +2 -5
- package/src/index.js +130 -20
package/dist/worker.jar
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groma/scanner-java",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Java compiler and project evidence; framework runtime use is not verified.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -44,10 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
],
|
|
46
46
|
"compatibility": {
|
|
47
|
-
"groma": "^0.3.0"
|
|
48
|
-
"technologyVersions": {
|
|
49
|
-
"java": "25.0.0"
|
|
50
|
-
}
|
|
47
|
+
"groma": "^0.3.0"
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
50
|
}
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// plugins/scanners/java/src/
|
|
3
|
-
import
|
|
2
|
+
// plugins/scanners/java/src/index.ts
|
|
3
|
+
import path7 from "path";
|
|
4
|
+
|
|
5
|
+
// plugins/scanners/projects.ts
|
|
6
|
+
import { execFile } from "child_process";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { promisify } from "util";
|
|
10
|
+
var execute = promisify(execFile);
|
|
11
|
+
var excluded = new Set([
|
|
12
|
+
".git",
|
|
13
|
+
"node_modules",
|
|
14
|
+
"vendor",
|
|
15
|
+
"target",
|
|
16
|
+
"dist",
|
|
17
|
+
"build",
|
|
18
|
+
"bin",
|
|
19
|
+
"obj",
|
|
20
|
+
".gradle",
|
|
21
|
+
".angular",
|
|
22
|
+
"coverage",
|
|
23
|
+
"generated",
|
|
24
|
+
"groma",
|
|
25
|
+
".groma"
|
|
26
|
+
]);
|
|
27
|
+
async function projectFiles(root, matches) {
|
|
28
|
+
const { stdout } = await execute("git", ["-C", root, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { maxBuffer: 64 * 1024 * 1024 });
|
|
29
|
+
return [...new Set(stdout.split("\x00").filter((file) => file && matches(file) && !file.split("/").some((part) => excluded.has(part)) && existsSync(path.join(root, file))))].sort();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// plugins/scanners/project-scanner.ts
|
|
4
33
|
import path3 from "path";
|
|
5
34
|
|
|
35
|
+
// plugins/scanners/observations.ts
|
|
36
|
+
import path2 from "path";
|
|
37
|
+
|
|
6
38
|
// packages/scanner/src/index.ts
|
|
7
39
|
function compare(...values) {
|
|
8
40
|
return values.join("\x00");
|
|
@@ -257,27 +289,101 @@ function sourcePosition(position) {
|
|
|
257
289
|
return { position: Number(position) };
|
|
258
290
|
}
|
|
259
291
|
|
|
292
|
+
// plugins/scanners/observations.ts
|
|
293
|
+
function relocateObservation(observation, directory) {
|
|
294
|
+
const file = (value) => path2.posix.normalize(path2.posix.join(directory, value));
|
|
295
|
+
return {
|
|
296
|
+
...observation,
|
|
297
|
+
roots: observation.roots.map((root) => ({ ...root, ...root.file ? { file: file(root.file) } : {} })),
|
|
298
|
+
files: observation.files.map((source) => ({ ...source, file: file(source.file) })),
|
|
299
|
+
operations: observation.operations?.map((operation) => ({ ...operation, file: file(operation.file) })),
|
|
300
|
+
invocations: observation.invocations?.map((call) => ({
|
|
301
|
+
...call,
|
|
302
|
+
...call.binding ? { binding: { ...call.binding, file: file(call.binding.file) } } : {}
|
|
303
|
+
})),
|
|
304
|
+
diagnostics: observation.diagnostics.map((item) => ({ ...item, ...item.file ? { file: file(item.file) } : {} }))
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function combineObservations(parts) {
|
|
308
|
+
if (!parts.length)
|
|
309
|
+
return;
|
|
310
|
+
const roots = [];
|
|
311
|
+
const files = new Map;
|
|
312
|
+
const operations = [];
|
|
313
|
+
const invocations = [];
|
|
314
|
+
const diagnostics = [];
|
|
315
|
+
for (const { key, observation } of parts) {
|
|
316
|
+
const id = (value) => JSON.stringify([key, value]);
|
|
317
|
+
roots.push(...observation.roots.map((root) => ({ ...root, id: id(root.id), ...root.parent ? { parent: id(root.parent) } : {} })));
|
|
318
|
+
for (const source of observation.files) {
|
|
319
|
+
const combined = files.get(source.file) ?? { file: source.file, roots: [], symbols: [] };
|
|
320
|
+
combined.roots.push(...source.roots.map(id));
|
|
321
|
+
combined.symbols.push(...source.symbols.map((symbol) => ({ ...symbol, id: id(symbol.id) })));
|
|
322
|
+
files.set(source.file, combined);
|
|
323
|
+
}
|
|
324
|
+
operations.push(...(observation.operations ?? []).map((operation) => ({ ...operation, id: id(operation.id) })));
|
|
325
|
+
invocations.push(...(observation.invocations ?? []).map((call) => ({ ...call, source: id(call.source), targets: call.targets.map(id) })));
|
|
326
|
+
diagnostics.push(...observation.diagnostics);
|
|
327
|
+
}
|
|
328
|
+
return createScanObservation({
|
|
329
|
+
scanner: parts[0].observation.scanner,
|
|
330
|
+
roots,
|
|
331
|
+
files: [...files.values()],
|
|
332
|
+
operations,
|
|
333
|
+
invocations,
|
|
334
|
+
diagnostics
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// plugins/scanners/project-scanner.ts
|
|
339
|
+
function projectScanner(scanner, select) {
|
|
340
|
+
return {
|
|
341
|
+
...scanner,
|
|
342
|
+
checkReadiness: async (root, settings = {}) => {
|
|
343
|
+
const projects = await select(root, settings);
|
|
344
|
+
if (!projects.length)
|
|
345
|
+
throw new Error(`${scanner.id}: No supported project declaration was found.`);
|
|
346
|
+
for (const project of projects)
|
|
347
|
+
await scanner.checkReadiness?.(project, settings);
|
|
348
|
+
},
|
|
349
|
+
scan: async (root, settings = {}) => {
|
|
350
|
+
const parts = [];
|
|
351
|
+
for (const project of await select(root, settings)) {
|
|
352
|
+
const observation = await scanner.scan(project, settings);
|
|
353
|
+
const key = path3.relative(root, project).split(path3.sep).join("/");
|
|
354
|
+
if (observation)
|
|
355
|
+
parts.push({ key, observation: relocateObservation(observation, key) });
|
|
356
|
+
}
|
|
357
|
+
return combineObservations(parts);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// plugins/scanners/java/src/adapter.ts
|
|
363
|
+
import { fileURLToPath } from "url";
|
|
364
|
+
import path6 from "path";
|
|
365
|
+
|
|
260
366
|
// plugins/scanners/java/src/maven.ts
|
|
261
367
|
import { access, mkdtemp, readFile, readdir, realpath, rm } from "fs/promises";
|
|
262
368
|
import os from "os";
|
|
263
|
-
import
|
|
369
|
+
import path5 from "path";
|
|
264
370
|
|
|
265
371
|
// plugins/scanners/java/src/process.ts
|
|
266
|
-
import { execFile } from "child_process";
|
|
267
|
-
import
|
|
372
|
+
import { execFile as execFile2 } from "child_process";
|
|
373
|
+
import path4 from "path";
|
|
268
374
|
function javaCommand() {
|
|
269
375
|
const executable = process.platform === "win32" ? "java.exe" : "java";
|
|
270
|
-
return process.env.JAVA_HOME ?
|
|
376
|
+
return process.env.JAVA_HOME ? path4.join(process.env.JAVA_HOME, "bin", executable) : executable;
|
|
271
377
|
}
|
|
272
378
|
function run(command, args, root, input = "", timeout = 120000) {
|
|
273
379
|
return new Promise((resolve, reject) => {
|
|
274
|
-
const child =
|
|
380
|
+
const child = execFile2(command, args, {
|
|
275
381
|
cwd: root,
|
|
276
382
|
encoding: "utf8",
|
|
277
383
|
timeout,
|
|
278
384
|
killSignal: "SIGKILL",
|
|
279
385
|
maxBuffer: 64 * 1024 * 1024,
|
|
280
|
-
windowsVerbatimArguments: process.platform === "win32" &&
|
|
386
|
+
windowsVerbatimArguments: process.platform === "win32" && path4.basename(command).toLowerCase() === "cmd.exe"
|
|
281
387
|
}, (error, stdout, stderr) => {
|
|
282
388
|
if (error)
|
|
283
389
|
reject(new Error(stderr.trim() || stdout.trim() || error.message));
|
|
@@ -308,16 +414,16 @@ async function exists(file) {
|
|
|
308
414
|
async function collect(root, directory) {
|
|
309
415
|
const files = [];
|
|
310
416
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
311
|
-
const file =
|
|
417
|
+
const file = path5.join(directory, entry.name);
|
|
312
418
|
if (entry.isDirectory())
|
|
313
419
|
files.push(...await collect(root, file));
|
|
314
420
|
else if (entry.isFile() && file.endsWith(".java"))
|
|
315
|
-
files.push(
|
|
421
|
+
files.push(path5.relative(root, file).split(path5.sep).join("/"));
|
|
316
422
|
}
|
|
317
423
|
return files.sort();
|
|
318
424
|
}
|
|
319
425
|
async function mavenCommand(root) {
|
|
320
|
-
const wrapper =
|
|
426
|
+
const wrapper = path5.join(root, process.platform === "win32" ? "mvnw.cmd" : "mvnw");
|
|
321
427
|
return await exists(wrapper) ? wrapper : process.platform === "win32" ? "mvn.cmd" : "mvn";
|
|
322
428
|
}
|
|
323
429
|
var mavenGoals = [
|
|
@@ -326,13 +432,13 @@ var mavenGoals = [
|
|
|
326
432
|
];
|
|
327
433
|
async function readJavaInput(repositoryRoot, java, worker, maven) {
|
|
328
434
|
const root = await realpath(repositoryRoot);
|
|
329
|
-
if (!await exists(
|
|
330
|
-
throw new Error("JAVA_UNSUPPORTED_BUILD: The Java scanner supports
|
|
435
|
+
if (!await exists(path5.join(root, "pom.xml"))) {
|
|
436
|
+
throw new Error("JAVA_UNSUPPORTED_BUILD: The Java scanner supports Maven pom.xml projects. Gradle and other build arrangements are not supported.");
|
|
331
437
|
}
|
|
332
|
-
const temporary = await mkdtemp(
|
|
438
|
+
const temporary = await mkdtemp(path5.join(os.tmpdir(), "groma-java-model-"));
|
|
333
439
|
try {
|
|
334
|
-
const model =
|
|
335
|
-
const classpath =
|
|
440
|
+
const model = path5.join(temporary, "pom.xml");
|
|
441
|
+
const classpath = path5.join(temporary, "classpath.txt");
|
|
336
442
|
const command = maven ?? await mavenCommand(root);
|
|
337
443
|
const args = [
|
|
338
444
|
"--offline",
|
|
@@ -350,12 +456,14 @@ async function readJavaInput(repositoryRoot, java, worker, maven) {
|
|
|
350
456
|
throw new Error(`JAVA_MAVEN_PREPARATION: Install the project JDK and Maven (or use its wrapper), then run ${command} ${mavenGoals.join(" ")} -DincludeScope=compile once with dependency access. Scans use Maven offline. ${error instanceof Error ? error.message : error}`);
|
|
351
457
|
}
|
|
352
458
|
const exported = JSON.parse(await run(java, ["-jar", worker, "model", model], root));
|
|
459
|
+
if (exported.aggregator)
|
|
460
|
+
return;
|
|
353
461
|
const files = await collect(root, exported.sourceRoot);
|
|
354
462
|
if (!files.length)
|
|
355
463
|
throw new Error("JAVA_EMPTY_SOURCE_SET: Maven main source directory contains no Java sources.");
|
|
356
464
|
if (files.some((file) => file.endsWith("module-info.java")))
|
|
357
465
|
throw new Error("JAVA_UNSUPPORTED_BUILD: JPMS module paths are not supported.");
|
|
358
|
-
const dependencies = (await readFile(classpath, "utf8")).trim().split(
|
|
466
|
+
const dependencies = (await readFile(classpath, "utf8")).trim().split(path5.delimiter).filter(Boolean);
|
|
359
467
|
if (await exists(exported.output))
|
|
360
468
|
dependencies.push(exported.output);
|
|
361
469
|
return {
|
|
@@ -391,6 +499,8 @@ async function checkJavaReadiness(repositoryRoot, options = {}) {
|
|
|
391
499
|
}
|
|
392
500
|
async function scanJavaSource(repositoryRoot, options = {}) {
|
|
393
501
|
const { input, command, jar } = await checkJavaReadiness(repositoryRoot, options);
|
|
502
|
+
if (!input)
|
|
503
|
+
return;
|
|
394
504
|
let stdout;
|
|
395
505
|
try {
|
|
396
506
|
stdout = await run(command, [
|
|
@@ -401,7 +511,7 @@ async function scanJavaSource(repositoryRoot, options = {}) {
|
|
|
401
511
|
input.release,
|
|
402
512
|
input.encoding,
|
|
403
513
|
input.generatedRoot
|
|
404
|
-
], input.root, `${input.classpath.join(
|
|
514
|
+
], input.root, `${input.classpath.join(path6.delimiter)}
|
|
405
515
|
${input.files.join(`
|
|
406
516
|
`)}
|
|
407
517
|
`, options.timeout);
|
|
@@ -416,13 +526,13 @@ ${input.files.join(`
|
|
|
416
526
|
// plugins/scanners/java/src/index.ts
|
|
417
527
|
var scanner = {
|
|
418
528
|
id: "java",
|
|
419
|
-
watch: { include: ["**/*.java", "pom.xml", "
|
|
529
|
+
watch: { include: ["**/*.java", "**/pom.xml", "**/.mvn/**"], exclude: [] },
|
|
420
530
|
checkReadiness: async (root) => {
|
|
421
531
|
await checkJavaReadiness(root);
|
|
422
532
|
},
|
|
423
533
|
scan: scanJavaSource
|
|
424
534
|
};
|
|
425
|
-
var src_default = scanner;
|
|
535
|
+
var src_default = projectScanner(scanner, async (root) => (await projectFiles(root, (file) => path7.posix.basename(file) === "pom.xml")).map((file) => path7.dirname(path7.join(root, file))));
|
|
426
536
|
export {
|
|
427
537
|
src_default as default
|
|
428
538
|
};
|