@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 +21 -0
- package/README.md +70 -0
- package/dist/core.d.ts +8 -0
- package/dist/core.js +116 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +57 -0
- package/package.json +48 -0
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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
}
|