@cleya/contributor-java-scip 0.0.0-stage → 1.0.0-next.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.
@@ -0,0 +1,28 @@
1
+ import type { Contributor, ContributorContext } from "@cleya/config";
2
+ import { type ScipFailure, type ScipGeneration } from "@cleya/contributor-scip";
3
+ import type { regionTrace } from "@cleya/region-trace";
4
+ import type { JavaScipOptions } from "./java-scip.types";
5
+ import { type BuildTool } from "./jvm/build-process";
6
+ import { type BinaryResolver } from "./requirements/binary.resolver";
7
+ /** scip-java args for the build tool (Maven needs batch-mode + skip tests). */
8
+ export declare const buildScipArgs: (buildTool: BuildTool, output: string) => string[];
9
+ /**
10
+ * Generate a SCIP index for a JVM project: detect the build tool, verify the
11
+ * environment, run `scip-java`, and translate a failed build into a readable
12
+ * error. Exported on its own so it can be tested without contributor plumbing.
13
+ */
14
+ export declare const runScipJava: (ctx: ContributorContext, options: JavaScipOptions, system: BinaryResolver) => Promise<ScipGeneration | ScipFailure>;
15
+ /**
16
+ * An indexer for JVM (Gradle or Maven) projects, backed by `scip-java` (which
17
+ * must be on PATH, alongside a JDK >= 17 and a Gradle/Maven wrapper or system
18
+ * binary). Decoding the resulting SCIP index into domain files is delegated to
19
+ * the shared {@link scipContributor}.
20
+ *
21
+ * @param options - Contributor options; see {@link JavaScipOptions}.
22
+ * @param system - Host machine access; injected in tests.
23
+ * @returns A trace contributor that generates and decodes a SCIP index.
24
+ * @example
25
+ * javaScipContributor({ buildTool: "gradle" }).region.id; // "trace"
26
+ * javaScipContributor({ output: "artifacts/java.scip" }).id; // "java-scip-contributor"
27
+ */
28
+ export declare function javaScipContributor(options?: JavaScipOptions, system?: BinaryResolver): Contributor<typeof regionTrace>;
@@ -0,0 +1,3 @@
1
+ export { javaScipContributor } from "./java-scip.contributor";
2
+ export type { JavaScipOptions } from "./java-scip.types";
3
+ export type { BuildTool } from "./jvm/build-process";
@@ -0,0 +1,226 @@
1
+ // @bun
2
+ // src/java-scip.contributor.ts
3
+ import {
4
+ prepareScipOutput,
5
+ scipContributor
6
+ } from "@cleya/contributor-scip";
7
+
8
+ // src/jvm/build-process.ts
9
+ import { existsSync } from "fs";
10
+ import { join } from "path";
11
+
12
+ // src/jvm/readable-error.ts
13
+ class ReadableError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "ReadableError";
17
+ }
18
+ }
19
+
20
+ // src/jvm/build-process.ts
21
+ var GRADLE_FILES = [
22
+ "build.gradle",
23
+ "build.gradle.kts",
24
+ "settings.gradle",
25
+ "settings.gradle.kts"
26
+ ];
27
+ var MAVEN_FILES = ["pom.xml"];
28
+ var JDK = [
29
+ /invalid source release/i,
30
+ /Unsupported class file major version/i,
31
+ /class file version/i
32
+ ];
33
+ var DEPS = [/Could not resolve/i, /Could not find/i, /Non-resolvable/i];
34
+ var TEST = [
35
+ /There were failing tests/i,
36
+ /Failed tests:/i,
37
+ /Tests run:.*(?:Failures|Errors): [1-9]/i
38
+ ];
39
+ var COMPILE = [/BUILD FAILED/i, /compilation failed/i, /\berror:/i];
40
+ var hasAny = (root, files) => files.some((file) => existsSync(join(root, file)));
41
+ var hasAnyGradleFile = (root) => hasAny(root, GRADLE_FILES);
42
+ var hasAnyMavenFile = (root) => hasAny(root, MAVEN_FILES);
43
+ var resolveBuildTool = (root, override) => {
44
+ if (override)
45
+ return override;
46
+ const gradle = hasAnyGradleFile(root);
47
+ const maven = hasAnyMavenFile(root);
48
+ if (gradle && maven)
49
+ throw new ReadableError(`Both Gradle and Maven detected at ${root}. Choose the build tool to index:
50
+ ` + ` --build-tool gradle (build.gradle, settings.gradle)
51
+ ` + " --build-tool maven (pom.xml)");
52
+ if (gradle)
53
+ return "gradle";
54
+ if (maven)
55
+ return "maven";
56
+ throw new ReadableError(`Neither Gradle nor Maven detected at ${root} ` + "(expected build.gradle[.kts]/settings.gradle[.kts] or pom.xml).");
57
+ };
58
+ var matchesAny = (text, signatures) => signatures.some((signature) => signature.test(text));
59
+ var diagnose = (result, buildTool) => {
60
+ const out = `${result.stdout}
61
+ ${result.stderr}`;
62
+ const head = `Build ${buildTool} (scip-java) failed (exit ${result.exitCode}).`;
63
+ const cmd = `
64
+ Command: ${result.command}`;
65
+ if (matchesAny(out, JDK))
66
+ return new ReadableError(`${head}
67
+ Likely JDK mismatch: check that JAVA_HOME points to a JDK compatible with the project.${cmd}`);
68
+ if (matchesAny(out, DEPS))
69
+ return new ReadableError(`${head}
70
+ Unresolved dependencies: check repository access (network / offline mode).${cmd}`);
71
+ if (matchesAny(out, TEST))
72
+ return new ReadableError(`${head}
73
+ Test failures: the project compiled but some tests failed (see the logs above).${cmd}`);
74
+ if (matchesAny(out, COMPILE))
75
+ return new ReadableError(`${head}
76
+ Compilation error: fix the build (see the logs above).${cmd}`);
77
+ return new ReadableError(`${head}
78
+ See the output above.${cmd}`);
79
+ };
80
+
81
+ // src/requirements/requirement.ts
82
+ import { existsSync as existsSync2 } from "fs";
83
+ import { join as join2 } from "path";
84
+
85
+ // src/requirements/version.utils.ts
86
+ var versionParts = (version) => version.split(".").map((part) => Number.parseInt(part, 10) || 0);
87
+ var parseVersion = (text) => {
88
+ const match = text.match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
89
+ return match ? match[0] : null;
90
+ };
91
+ var satisfiesMin = (version, minimum) => {
92
+ const actual = versionParts(version);
93
+ const required = versionParts(minimum);
94
+ for (let i = 0;i < Math.max(actual.length, required.length); i++) {
95
+ const actualPart = actual[i] ?? 0;
96
+ const requiredPart = required[i] ?? 0;
97
+ if (actualPart > requiredPart)
98
+ return true;
99
+ if (actualPart < requiredPart)
100
+ return false;
101
+ }
102
+ return true;
103
+ };
104
+
105
+ // src/requirements/requirement.ts
106
+ var MIN_JDK = "17";
107
+ var JDK_HINT = "install JDK Temurin 17/21";
108
+ var SCIP_JAVA_HINT = "install scip-java \u2014 https://sourcegraph.github.io/scip-java/docs/getting-started.html";
109
+ var BUILD_TOOL_HINT = "install the build tool or add its wrapper";
110
+ var issue = (tool, installHint, reason = "not found in PATH") => ({ tool, reason, installHint });
111
+ var requireBinary = (name, installHint) => async (ctx) => await ctx.system.resolve(name) ? null : issue(name, installHint);
112
+ var scipJavaRequirement = requireBinary("scip-java", SCIP_JAVA_HINT);
113
+ var jdkRequirement = async (ctx) => {
114
+ const java = await ctx.system.resolve("java", ["-version"]);
115
+ if (!java?.version)
116
+ return issue("java", JDK_HINT);
117
+ if (!satisfiesMin(java.version, MIN_JDK))
118
+ return issue("java", JDK_HINT, `version too old (detected ${java.version}, requires ${MIN_JDK}+)`);
119
+ return null;
120
+ };
121
+ var hasUsableBuildTool = async (buildTool, root, resolve) => {
122
+ const repoWrapper = buildTool === "gradle" ? "gradlew" : "mvnw";
123
+ const systemTool = buildTool === "gradle" ? "gradle" : "mvn";
124
+ if (existsSync2(join2(root, repoWrapper)))
125
+ return true;
126
+ return await resolve(systemTool) != null;
127
+ };
128
+ var buildToolRequirement = async (ctx) => {
129
+ if (await hasUsableBuildTool(ctx.buildTool, ctx.projectRoot, ctx.system.resolve))
130
+ return null;
131
+ const tool = ctx.buildTool === "gradle" ? "gradlew|gradle" : "mvnw|mvn";
132
+ return issue(tool, BUILD_TOOL_HINT);
133
+ };
134
+ var runRequirements = async (requirements, ctx) => {
135
+ const results = await Promise.all(requirements.map((r) => r(ctx)));
136
+ return results.filter((r) => r !== null);
137
+ };
138
+ var formatMissing = (issues, subject) => {
139
+ const lines = issues.map((i) => ` - ${i.tool}: ${i.reason} \u2192 ${i.installHint}`);
140
+ return `Missing requirements for ${subject}:
141
+ ${lines.join(`
142
+ `)}`;
143
+ };
144
+
145
+ // src/requirements/index.ts
146
+ var JAVA_SCIP_REQUIREMENTS = [
147
+ scipJavaRequirement,
148
+ jdkRequirement,
149
+ buildToolRequirement
150
+ ];
151
+
152
+ // src/requirements/binary.resolver.ts
153
+ var readVersion = async (binPath, args) => {
154
+ try {
155
+ const proc = Bun.spawn([binPath, ...args], {
156
+ stdout: "pipe",
157
+ stderr: "pipe"
158
+ });
159
+ const [stdout, stderr] = await Promise.all([
160
+ new Response(proc.stdout).text(),
161
+ new Response(proc.stderr).text()
162
+ ]);
163
+ if (await proc.exited !== 0)
164
+ return null;
165
+ return parseVersion(`${stdout}
166
+ ${stderr}`);
167
+ } catch {
168
+ return null;
169
+ }
170
+ };
171
+ var binaryResolver = {
172
+ resolve: async (name, versionArgs) => {
173
+ const binPath = Bun.which(name);
174
+ if (!binPath)
175
+ return null;
176
+ if (!versionArgs)
177
+ return { binPath, version: null };
178
+ return { binPath, version: await readVersion(binPath, versionArgs) };
179
+ }
180
+ };
181
+
182
+ // src/java-scip.contributor.ts
183
+ var INDEXER_ID = "java-scip";
184
+ var CONTRIBUTOR_ID = `${INDEXER_ID}-contributor`;
185
+ var buildScipArgs = (buildTool, output) => {
186
+ const base = ["index", "--build-tool", buildTool, "--output", output];
187
+ if (buildTool === "maven")
188
+ return [...base, "--", "--batch-mode", "-DskipTests", "package"];
189
+ return base;
190
+ };
191
+ var runScipJava = async (ctx, options, system) => {
192
+ try {
193
+ const buildTool = resolveBuildTool(ctx.root, options.buildTool);
194
+ const issues = await runRequirements(JAVA_SCIP_REQUIREMENTS, {
195
+ projectRoot: ctx.root,
196
+ buildTool,
197
+ system
198
+ });
199
+ if (issues.length)
200
+ return { error: formatMissing(issues, "scip-java") };
201
+ const output = await prepareScipOutput(ctx, options.output);
202
+ const argv = ["scip-java", ...buildScipArgs(buildTool, output)];
203
+ const result = await ctx.exec({ argv, cwd: ctx.root });
204
+ if (result.code !== 0) {
205
+ const run = {
206
+ exitCode: result.code,
207
+ stdout: result.stdout,
208
+ stderr: result.stderr,
209
+ command: argv.join(" ")
210
+ };
211
+ return { error: diagnose(run, buildTool).message };
212
+ }
213
+ return { scipPath: output };
214
+ } catch (e) {
215
+ return { error: e instanceof Error ? e.message : String(e) };
216
+ }
217
+ };
218
+ function javaScipContributor(options = {}, system = binaryResolver) {
219
+ return scipContributor({
220
+ id: CONTRIBUTOR_ID,
221
+ generate: (ctx) => runScipJava(ctx, options, system)
222
+ });
223
+ }
224
+ export {
225
+ javaScipContributor
226
+ };
@@ -0,0 +1,8 @@
1
+ import type { BuildTool } from "./jvm/build-process";
2
+ /** Options of `javaScipContributor`. */
3
+ export interface JavaScipOptions {
4
+ /** Override relative to the workspace root. Defaults to `index.scip` in the contributor's artifacts directory under the config's `.cleya`. */
5
+ output?: string;
6
+ /** Force the build tool, skipping filesystem detection. */
7
+ buildTool?: BuildTool;
8
+ }
@@ -0,0 +1,32 @@
1
+ import { ReadableError } from "./readable-error";
2
+ export type BuildTool = "gradle" | "maven";
3
+ /** Captured result of a finished process run (shape `diagnose` needs). */
4
+ export interface RunResult {
5
+ exitCode: number;
6
+ stdout: string;
7
+ stderr: string;
8
+ command: string;
9
+ }
10
+ /**
11
+ * Resolve the build tool driving the project at `root`. `override` short-circuits
12
+ * file inspection. Throws a {@link ReadableError} when both tools are present
13
+ * (ambiguous) or neither is (nothing to build).
14
+ *
15
+ * @example
16
+ * resolveBuildTool("/repo") // → "gradle" (build.gradle present)
17
+ * resolveBuildTool("/repo", "maven") // → "maven" (override, no inspection)
18
+ * resolveBuildTool("/repo-with-both") // → throws ReadableError
19
+ */
20
+ export declare const resolveBuildTool: (root: string, override?: BuildTool) => BuildTool;
21
+ /**
22
+ * Translate a failed scip-java run into a user-readable error (best-effort).
23
+ * The output is matched against known failure signatures, most specific first;
24
+ * an unrecognized failure still yields an error pointing at the logs.
25
+ *
26
+ * @example
27
+ * diagnose({ exitCode: 1, stdout: "", stderr: "invalid source release: 17", command: "…" }, "gradle")
28
+ * // → ReadableError: "Build gradle (scip-java) failed (exit 1).
29
+ * // Likely JDK mismatch: check that JAVA_HOME points to a JDK compatible with the project.
30
+ * // Command: …"
31
+ */
32
+ export declare const diagnose: (result: RunResult, buildTool: BuildTool) => ReadableError;
@@ -0,0 +1,4 @@
1
+ /** An error whose message is already formatted for the end user. */
2
+ export declare class ReadableError extends Error {
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Read-only access to the host machine (PATH lookups, process output). The only
3
+ * impure module of the plugin — injected in tests.
4
+ */
5
+ /** A binary found on PATH, with its version when one was asked for and read. */
6
+ export interface ResolveResult {
7
+ /** Absolute path of the binary on PATH. */
8
+ binPath: string;
9
+ /** Version reported by the binary. Null when unasked, unrun, or unparseable. */
10
+ version: string | null;
11
+ }
12
+ export interface BinaryResolver {
13
+ /**
14
+ * Locates `name` on PATH. Null when `name` is absent.
15
+ *
16
+ * Reading a version costs a process launch — hundreds of milliseconds for a
17
+ * JVM launcher like `scip-java` — so it happens only on demand: pass the
18
+ * flags the binary answers to (`["--version"]` for most, `["-version"]` for
19
+ * `java`). Omit them for a presence check and `version` comes back null
20
+ * without anything being run.
21
+ */
22
+ resolve: (name: string, versionArgs?: string[]) => Promise<ResolveResult | null>;
23
+ }
24
+ /** Real system access: PATH lookup via Bun.which, version via a captured spawn. */
25
+ export declare const binaryResolver: BinaryResolver;
@@ -0,0 +1,6 @@
1
+ import { type Requirement } from "./requirement";
2
+ /**
3
+ * The environment a scip-java run requires. Order only affects the order of the
4
+ * reported issues. Add a requirement in `requirement.ts` and list it here.
5
+ */
6
+ export declare const JAVA_SCIP_REQUIREMENTS: Requirement[];
@@ -0,0 +1,45 @@
1
+ import type { BuildTool } from "../jvm/build-process";
2
+ import type { BinaryResolver } from "./binary.resolver";
3
+ /**
4
+ * Every environment requirement a scip-java run has, and the machinery to run
5
+ * them. Each requirement owns its own policy and its own message — nothing else
6
+ * knows them. List them in JAVA_SCIP_REQUIREMENTS (index.ts).
7
+ */
8
+ /** A single unmet environment requirement, reported uniformly. */
9
+ export interface RequirementIssue {
10
+ tool: string;
11
+ reason: string;
12
+ installHint: string;
13
+ }
14
+ /** Everything a requirement may inspect. Requirements receive it; they take nothing else. */
15
+ export interface RequirementContext {
16
+ projectRoot: string;
17
+ buildTool: BuildTool;
18
+ system: BinaryResolver;
19
+ }
20
+ /**
21
+ * One environment requirement. Returns the issue when unmet, null when
22
+ * satisfied.
23
+ */
24
+ export type Requirement = (ctx: RequirementContext) => Promise<RequirementIssue | null>;
25
+ /** Uniform issue constructor. */
26
+ export declare const issue: (tool: string, installHint: string, reason?: string) => RequirementIssue;
27
+ /** Requires a binary on PATH — the shape every binary presence check shares. */
28
+ export declare const requireBinary: (name: string, installHint: string) => Requirement;
29
+ /** Requires the `scip-java` binary on PATH. */
30
+ export declare const scipJavaRequirement: Requirement;
31
+ /**
32
+ * Requires a JDK >= 17 on PATH. Sole owner of the minimum-version policy —
33
+ * `version.utils` compares, but knows no minimum. `java` spells its version
34
+ * flag `-version`, not `--version`.
35
+ */
36
+ export declare const jdkRequirement: Requirement;
37
+ /** Requires a usable build tool: a repo wrapper, or the binary on PATH. */
38
+ export declare const buildToolRequirement: Requirement;
39
+ /**
40
+ * Run every requirement and collect what is missing, in declaration order. An
41
+ * empty array means the environment is ready.
42
+ */
43
+ export declare const runRequirements: (requirements: Requirement[], ctx: RequirementContext) => Promise<RequirementIssue[]>;
44
+ /** Render unmet requirements into one user-readable message. */
45
+ export declare const formatMissing: (issues: RequirementIssue[], subject: string) => string;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Version handling for noisy tool output: extracting a version from it, and
3
+ * comparing it to a minimum. Pure — no PATH, no process, no policy. Each
4
+ * requirement owns the minimum it enforces; this module only answers the
5
+ * comparison.
6
+ */
7
+ /**
8
+ * Extract the first version-like token from noisy text.
9
+ *
10
+ * @example
11
+ * parseVersion('openjdk version "17.0.9" 2023-10-17') // → "17.0.9"
12
+ * parseVersion("command not found") // → null
13
+ */
14
+ export declare const parseVersion: (text: string) => string | null;
15
+ /**
16
+ * True when `version` >= `minimum`, compared part by part. A missing part
17
+ * counts as 0, so "17" satisfies "17.0.0".
18
+ *
19
+ * @example
20
+ * satisfiesMin("17.0.9", "17") // → true
21
+ * satisfiesMin("1.8.0", "17") // → false (legacy 1.x numbering)
22
+ */
23
+ export declare const satisfiesMin: (version: string, minimum: string) => boolean;
package/package.json CHANGED
@@ -1,6 +1,37 @@
1
1
  {
2
- "name": "@cleya/contributor-java-scip",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@cleya/contributor-java-scip",
3
+ "description": "Indexes Java symbols and their references",
4
+ "cleya": {
5
+ "category": "contributor"
6
+ },
7
+ "version": "1.0.0-next.0",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/java-scip.index.d.ts",
12
+ "default": "./dist/java-scip.index.js"
13
+ }
14
+ },
15
+ "types": "./dist/java-scip.index.d.ts",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "bun build ./src/java-scip.index.ts --outdir dist --target bun --external @cleya/config --external @cleya/region-trace --external @cleya/contributor-scip && bunx tsc -p tsconfig.build.json"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@cleya/contributor-scip": "^1.0.0-next.0"
27
+ },
28
+ "peerDependencies": {
29
+ "@cleya/config": "1.0.0-next.0",
30
+ "@cleya/region-trace": "1.0.0-next.0",
31
+ "typescript": "^5"
32
+ },
33
+ "devDependencies": {
34
+ "@cleya/config": "1.0.0-next.0",
35
+ "@cleya/region-trace": "1.0.0-next.0"
36
+ }
37
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.