@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 CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groma/scanner-java",
3
- "version": "0.1.0",
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/adapter.ts
3
- import { fileURLToPath } from "url";
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 path2 from "path";
369
+ import path5 from "path";
264
370
 
265
371
  // plugins/scanners/java/src/process.ts
266
- import { execFile } from "child_process";
267
- import path from "path";
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 ? path.join(process.env.JAVA_HOME, "bin", executable) : executable;
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 = execFile(command, args, {
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" && path.basename(command).toLowerCase() === "cmd.exe"
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 = path2.join(directory, entry.name);
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(path2.relative(root, file).split(path2.sep).join("/"));
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 = path2.join(root, process.platform === "win32" ? "mvnw.cmd" : "mvnw");
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(path2.join(root, "pom.xml"))) {
330
- throw new Error("JAVA_UNSUPPORTED_BUILD: The Java scanner supports a root single-module Maven pom.xml. Gradle and other build arrangements are not supported.");
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(path2.join(os.tmpdir(), "groma-java-model-"));
438
+ const temporary = await mkdtemp(path5.join(os.tmpdir(), "groma-java-model-"));
333
439
  try {
334
- const model = path2.join(temporary, "pom.xml");
335
- const classpath = path2.join(temporary, "classpath.txt");
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(path2.delimiter).filter(Boolean);
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(path3.delimiter)}
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", ".mvn/**"], exclude: [] },
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
  };