@franzmoca/opencode-lombok 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Francesco Moca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # opencode-lombok
2
+
3
+ `opencode-lombok` is an OpenCode plugin that adds Lombok support to Java projects.
4
+
5
+ This plugin was inspired by [opencode PR #8031](https://github.com/anomalyco/opencode/pull/8031).
6
+
7
+ It automatically:
8
+
9
+ - detects Lombok usage in `pom.xml`, `build.gradle`, and `build.gradle.kts`
10
+ - downloads `lombok.jar` when needed
11
+ - configures `JAVA_TOOL_OPTIONS` with `-javaagent:<path-to-lombok.jar>` so JVM-based LSP servers (including JDTLS) pick it up
12
+
13
+ If `JAVA_TOOL_OPTIONS` already exists, the plugin appends the Lombok javaagent and avoids duplicates.
14
+
15
+ ## Install
16
+
17
+ Add the plugin to your OpenCode config:
18
+
19
+ ```json
20
+ {
21
+ "$schema": "https://opencode.ai/config.json",
22
+ "plugin": ["@franzmoca/opencode-lombok"]
23
+ }
24
+ ```
25
+
26
+ You can also pin a specific version:
27
+
28
+ ```json
29
+ {
30
+ "$schema": "https://opencode.ai/config.json",
31
+ "plugin": ["@franzmoca/opencode-lombok@0.1.0"]
32
+ }
33
+ ```
34
+
35
+ OpenCode will install npm plugins automatically at startup.
36
+
37
+ ## Behavior
38
+
39
+ - If `lsp` is globally disabled (`"lsp": false`), the plugin does nothing.
40
+ - If `jdtls` is explicitly disabled (`"lsp": { "jdtls": { "disabled": true } }`), the plugin does nothing.
41
+ - If `OPENCODE_DISABLE_LSP_DOWNLOAD=true`, the plugin will not download `lombok.jar`.
42
+
43
+ ## Development
44
+
45
+ ```bash
46
+ bun install
47
+ bun run typecheck
48
+ bun test
49
+ bun run build
50
+ ```
51
+
52
+ ## Publish to npm
53
+
54
+ ```bash
55
+ npm login
56
+ npm publish --access public
57
+ ```
58
+
59
+ Or use the GitHub Actions workflow:
60
+
61
+ - `CI` runs typecheck, tests, and build on push/PR.
62
+ - `Publish` runs on tags (`v*`) and publishes with `NPM_TOKEN`.
63
+
64
+ ### Required GitHub secret
65
+
66
+ - `NPM_TOKEN`: npm automation token with publish rights for `@franzmoca/opencode-lombok`
67
+
68
+ ## License
69
+
70
+ MIT
package/dist/core.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare function hasLombokDependency(content: string): boolean;
2
+ export declare function detectLombokDependency(projectRoot: string): Promise<boolean>;
3
+ export declare function opencodeDataDir(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, home?: string): string;
4
+ export declare function lombokJarPath(dataDir?: string): string;
5
+ export declare function isLspDownloadDisabled(env?: NodeJS.ProcessEnv): boolean;
6
+ export declare function ensureLombokJar(file: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
7
+ export declare function formatJavaAgentArg(file: string): string;
8
+ export declare function mergeJavaToolOptions(current: string | undefined, file: string): string;
package/dist/core.js ADDED
@@ -0,0 +1,116 @@
1
+ import fs from "fs/promises";
2
+ import os from "os";
3
+ import path from "path";
4
+ const LOMBOK_URL = "https://projectlombok.org/downloads/lombok.jar";
5
+ const BUILD_FILES = new Set(["pom.xml", "build.gradle", "build.gradle.kts"]);
6
+ const SKIP_DIRS = new Set([
7
+ ".git",
8
+ "node_modules",
9
+ "dist",
10
+ "build",
11
+ "target",
12
+ "out",
13
+ ".next",
14
+ ".turbo",
15
+ ]);
16
+ const LOMBOK_MATCHERS = [
17
+ /<artifactId>\s*lombok\s*<\/artifactId>/,
18
+ /["']org\.projectlombok:lombok[:"']/,
19
+ /io\.freefair\.lombok/,
20
+ ];
21
+ const LOMBOK_JAVA_AGENT = /-javaagent:(?:"[^"]*lombok\.jar"|[^ "']*lombok\.jar)/i;
22
+ const pathExists = async (file) => fs
23
+ .stat(file)
24
+ .then(() => true)
25
+ .catch(() => false);
26
+ const readEntries = async (dir) => {
27
+ return fs.readdir(dir, { withFileTypes: true }).catch(() => []);
28
+ };
29
+ const readText = async (file) => {
30
+ return fs.readFile(file, "utf8").catch(() => "");
31
+ };
32
+ export function hasLombokDependency(content) {
33
+ return LOMBOK_MATCHERS.some((matcher) => matcher.test(content));
34
+ }
35
+ const containsLombokInFile = async (file) => {
36
+ const content = await readText(file);
37
+ if (!content)
38
+ return false;
39
+ return hasLombokDependency(content);
40
+ };
41
+ async function detectInDir(dir) {
42
+ const entries = await readEntries(dir);
43
+ for (const entry of entries) {
44
+ const file = path.join(dir, entry.name);
45
+ if (entry.isFile() && BUILD_FILES.has(entry.name)) {
46
+ const hit = await containsLombokInFile(file);
47
+ if (hit)
48
+ return true;
49
+ }
50
+ if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
51
+ const hit = await detectInDir(file);
52
+ if (hit)
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ export async function detectLombokDependency(projectRoot) {
59
+ return detectInDir(projectRoot);
60
+ }
61
+ export function opencodeDataDir(platform = process.platform, env = process.env, home = os.homedir()) {
62
+ if (env.XDG_DATA_HOME)
63
+ return path.join(env.XDG_DATA_HOME, "opencode");
64
+ if (platform === "darwin")
65
+ return path.join(home, "Library", "Application Support", "opencode");
66
+ if (platform === "win32") {
67
+ const appData = env.APPDATA || path.win32.join(home, "AppData", "Roaming");
68
+ return path.win32.join(appData, "opencode");
69
+ }
70
+ return path.join(home, ".local", "share", "opencode");
71
+ }
72
+ export function lombokJarPath(dataDir = opencodeDataDir()) {
73
+ return path.join(dataDir, "bin", "jdtls", "bin", "lombok.jar");
74
+ }
75
+ export function isLspDownloadDisabled(env = process.env) {
76
+ const value = env.OPENCODE_DISABLE_LSP_DOWNLOAD || "";
77
+ return /^(1|true|yes)$/i.test(value.trim());
78
+ }
79
+ export async function ensureLombokJar(file, env = process.env) {
80
+ const exists = await pathExists(file);
81
+ if (exists)
82
+ return file;
83
+ if (isLspDownloadDisabled(env))
84
+ return undefined;
85
+ await fs.mkdir(path.dirname(file), { recursive: true });
86
+ const response = await fetch(LOMBOK_URL).catch(() => undefined);
87
+ if (!response?.ok)
88
+ return undefined;
89
+ const body = await response.arrayBuffer().catch(() => undefined);
90
+ if (!body)
91
+ return undefined;
92
+ const saved = await fs
93
+ .writeFile(file, Buffer.from(body))
94
+ .then(() => true)
95
+ .catch(() => false);
96
+ if (!saved)
97
+ return undefined;
98
+ const present = await pathExists(file);
99
+ if (!present)
100
+ return undefined;
101
+ return file;
102
+ }
103
+ export function formatJavaAgentArg(file) {
104
+ if (!/\s/.test(file))
105
+ return `-javaagent:${file}`;
106
+ return `-javaagent:"${file.replace(/"/g, '\\"')}"`;
107
+ }
108
+ export function mergeJavaToolOptions(current, file) {
109
+ const existing = current?.trim() || "";
110
+ if (LOMBOK_JAVA_AGENT.test(existing))
111
+ return existing;
112
+ const agent = formatJavaAgentArg(file);
113
+ if (!existing)
114
+ return agent;
115
+ return `${existing} ${agent}`;
116
+ }
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const LombokPlugin: Plugin;
3
+ export default LombokPlugin;
4
+ export * from "./core.js";
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ import { detectLombokDependency, ensureLombokJar, isLspDownloadDisabled, lombokJarPath, mergeJavaToolOptions, } from "./core.js";
2
+ const SERVICE = "plugin.lombok";
3
+ function jdtlsDisabled(lsp) {
4
+ if (!lsp || typeof lsp !== "object")
5
+ return false;
6
+ if (!("jdtls" in lsp))
7
+ return false;
8
+ const jdtls = lsp.jdtls;
9
+ if (!jdtls || typeof jdtls !== "object")
10
+ return false;
11
+ return Boolean(jdtls.disabled);
12
+ }
13
+ async function log(ctx, level, message, extra) {
14
+ await ctx.client.app
15
+ .log({
16
+ body: {
17
+ service: SERVICE,
18
+ level,
19
+ message,
20
+ extra,
21
+ },
22
+ })
23
+ .catch(() => { });
24
+ }
25
+ export const LombokPlugin = async (ctx) => {
26
+ return {
27
+ config: async (config) => {
28
+ if (config.lsp === false)
29
+ return;
30
+ if (jdtlsDisabled(config.lsp))
31
+ return;
32
+ const lombokDetected = await detectLombokDependency(ctx.directory);
33
+ if (!lombokDetected)
34
+ return;
35
+ await log(ctx, "info", "Lombok dependency detected");
36
+ const jar = await ensureLombokJar(lombokJarPath());
37
+ if (!jar) {
38
+ await log(ctx, "warn", "Lombok detected but lombok.jar is unavailable", {
39
+ downloadDisabled: isLspDownloadDisabled(),
40
+ });
41
+ return;
42
+ }
43
+ const current = process.env.JAVA_TOOL_OPTIONS;
44
+ const next = mergeJavaToolOptions(current, jar);
45
+ process.env.JAVA_TOOL_OPTIONS = next;
46
+ if (next === (current?.trim() || "")) {
47
+ await log(ctx, "info", "JAVA_TOOL_OPTIONS already has a Lombok javaagent", { jarPath: jar });
48
+ return;
49
+ }
50
+ await log(ctx, "info", "Configured Lombok javaagent for JVM-based language servers", {
51
+ jarPath: jar,
52
+ });
53
+ },
54
+ };
55
+ };
56
+ export default LombokPlugin;
57
+ export * from "./core.js";
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/package.json",
3
+ "name": "@franzmoca/opencode-lombok",
4
+ "version": "0.1.0",
5
+ "description": "OpenCode plugin that adds Lombok support for JDTLS",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/franzmoca/opencode-lombok.git"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.json",
27
+ "typecheck": "tsc -p tsconfig.json --noEmit",
28
+ "test": "bun test",
29
+ "prepublishOnly": "bun run typecheck && bun test && bun run build"
30
+ },
31
+ "keywords": [
32
+ "opencode",
33
+ "plugin",
34
+ "lombok",
35
+ "jdtls",
36
+ "java"
37
+ ],
38
+ "engines": {
39
+ "bun": ">=1.3.0"
40
+ },
41
+ "dependencies": {
42
+ "@opencode-ai/plugin": "^1.1.14"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.13.9",
46
+ "typescript": "^5.8.2"
47
+ }
48
+ }