claudeos-core 2.5.0 → 2.5.2
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/CHANGELOG.md +88 -0
- package/README.de.md +4 -2
- package/README.es.md +4 -2
- package/README.fr.md +4 -2
- package/README.hi.md +4 -2
- package/README.ja.md +4 -2
- package/README.ko.md +4 -2
- package/README.md +4 -2
- package/README.ru.md +4 -2
- package/README.vi.md +4 -2
- package/README.zh-CN.md +4 -2
- package/lib/env-parser.js +198 -4
- package/package.json +92 -92
- package/pass-json-validator/index.js +1 -1
- package/pass-prompts/templates/java-spring/pass1.md +10 -2
- package/pass-prompts/templates/java-spring/pass3.md +2 -1
- package/plan-installer/index.js +17 -1
- package/plan-installer/jvm-detect.js +562 -0
- package/plan-installer/scanners/scan-java.js +82 -14
- package/plan-installer/stack-detector.js +471 -34
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeOS-Core — JVM / Spring detection helpers
|
|
3
|
+
*
|
|
4
|
+
* Pure text functions. No filesystem access, no side effects: every function
|
|
5
|
+
* takes the build-file text (or a file list) and returns what that text
|
|
6
|
+
* literally declares. Nothing here invents a version — every returned
|
|
7
|
+
* version string is a substring of the input, or is resolved from a variable
|
|
8
|
+
* that is itself defined in the input.
|
|
9
|
+
*
|
|
10
|
+
* Why this exists (v2.5.x): stack-detector's Gradle branch only set
|
|
11
|
+
* `language = "java"` when it saw the string `spring-boot`. A legacy
|
|
12
|
+
* `apply plugin: 'java'` + `spring-webmvc:4.3.30.RELEASE` build reported
|
|
13
|
+
* `language: null` → "No language detected" → the Java scanner never ran.
|
|
14
|
+
* Maven set the language but never the framework or its version. Ant and
|
|
15
|
+
* Eclipse-WTP projects (build.xml, .classpath, WEB-INF/lib/*.jar — the
|
|
16
|
+
* shape of most Korean SI legacy) were invisible.
|
|
17
|
+
*
|
|
18
|
+
* Scope of "Spring Framework" here: group id EXACTLY `org.springframework`.
|
|
19
|
+
* `org.springframework.boot` / `.data` / `.security` / `.cloud` are other
|
|
20
|
+
* projects with their own version lines and are deliberately excluded from
|
|
21
|
+
* the framework-version search — a `spring-security-core:5.8.0` must never
|
|
22
|
+
* be reported as Spring Framework 5.8.0.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// ─── Version token ───────────────────────────────────────────────────
|
|
26
|
+
// Accepts `5.3.30`, `4.3.30.RELEASE`, `3.2.18.RELEASE`, `2.5.6`, `2.5.6.SEC03`,
|
|
27
|
+
// `6.1.0-M2`, `5.3.0-SNAPSHOT`. Must start with a digit.
|
|
28
|
+
const VER = "(\\d+(?:\\.\\d+){1,3}(?:[.-][A-Za-z0-9]+)*)";
|
|
29
|
+
|
|
30
|
+
// Spring Framework artifact names (group `org.springframework` only).
|
|
31
|
+
// `spring` alone is the 2.x-era single jar (`org.springframework:spring:2.5.6`).
|
|
32
|
+
// Spring 1.x published under the bare group `springframework`; 2.0+ under
|
|
33
|
+
// `org.springframework`. Both are the Framework; nothing else is.
|
|
34
|
+
const SPRING_GROUP = "(?:org\\.)?springframework";
|
|
35
|
+
const SPRING_FW_ARTIFACT = "spring(?:-(?:core|beans|context|context-support|context-indexer|aop|aspects|expression|instrument|jcl|jdbc|jms|messaging|orm|oxm|r2dbc|test|tx|web|webflux|webmvc|websocket|framework-bom|struts|ibatis|hibernate3|mock|agent|portlet|dao|support|remoting))?";
|
|
36
|
+
|
|
37
|
+
// ─── Gradle ──────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Which JVM plugins does a Gradle build file apply?
|
|
41
|
+
* Handles the legacy `apply plugin: 'x'`, the `plugins { id 'x' }` DSL,
|
|
42
|
+
* the Kotlin-DSL `id("x")`, and Kotlin-DSL bare identifiers (`java`,
|
|
43
|
+
* `war`, `` `java-library` ``, `application`).
|
|
44
|
+
*/
|
|
45
|
+
function gradleJvmPlugins(g) {
|
|
46
|
+
const found = new Set();
|
|
47
|
+
const add = (p) => { if (p) found.add(p); };
|
|
48
|
+
const PLUGINS = ["java", "java-library", "war", "ear", "application", "groovy", "org.springframework.boot", "spring-boot", "io.spring.dependency-management"];
|
|
49
|
+
for (const p of PLUGINS) {
|
|
50
|
+
const esc = p.replace(/[.-]/g, "\\$&");
|
|
51
|
+
if (new RegExp(`apply\\s+plugin:\\s*['"]${esc}['"]`).test(g)) add(p);
|
|
52
|
+
// Kotlin DSL legacy form: apply(plugin = "war")
|
|
53
|
+
if (new RegExp(`apply\\s*\\(\\s*plugin\\s*=\\s*['"]${esc}['"]\\s*\\)`).test(g)) add(p);
|
|
54
|
+
if (new RegExp(`\\bid\\s*\\(?\\s*['"]${esc}['"]`).test(g)) add(p);
|
|
55
|
+
}
|
|
56
|
+
// Kotlin DSL bare identifiers inside a plugins { } block only.
|
|
57
|
+
const pluginsBlock = g.match(/plugins\s*\{([\s\S]*?)\}/);
|
|
58
|
+
if (pluginsBlock) {
|
|
59
|
+
const body = pluginsBlock[1];
|
|
60
|
+
for (const line of body.split("\n")) {
|
|
61
|
+
const t = line.trim().replace(/\/\/.*$/, "").trim();
|
|
62
|
+
if (/^(java|war|ear|application|groovy)$/.test(t)) add(t);
|
|
63
|
+
if (/^`java-library`$/.test(t)) add("java-library");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function gradlePackaging(plugins) {
|
|
70
|
+
if (plugins.has("ear")) return "ear";
|
|
71
|
+
if (plugins.has("war")) return "war";
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function gradleIsJvm(plugins) {
|
|
76
|
+
return ["java", "java-library", "war", "ear", "application", "groovy", "org.springframework.boot", "spring-boot"]
|
|
77
|
+
.some(p => plugins.has(p));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Does the Gradle build declare a Spring Framework dependency
|
|
82
|
+
* (group EXACTLY org.springframework)?
|
|
83
|
+
*/
|
|
84
|
+
function gradleHasSpringFramework(g) {
|
|
85
|
+
// `org.springframework:spring-webmvc:…` / `org.springframework:spring:…`
|
|
86
|
+
// The lookahead forbids `org.springframework.boot:` etc. by requiring `:`
|
|
87
|
+
// immediately after the group.
|
|
88
|
+
if (new RegExp(`['"]${SPRING_GROUP}:${SPRING_FW_ARTIFACT}(?:[:'"]|$)`, "m").test(g)) return true;
|
|
89
|
+
// group: 'org.springframework', name: 'spring-webmvc' — any key order
|
|
90
|
+
if (gradleMapDeps(g).some(d => d.isSpringFw)) return true;
|
|
91
|
+
// mavenBom 'org.springframework:spring-framework-bom:…'
|
|
92
|
+
if (/spring-framework-bom/.test(g)) return true;
|
|
93
|
+
// version catalog accessor `libs.spring.webmvc` cannot be resolved here;
|
|
94
|
+
// the catalog itself is inspected by the caller.
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Resolve `${name}` / `$name` / `name` against `ext { name = '…' }`,
|
|
100
|
+
* `def name = '…'`, `val name = "…"`, `name = "…"` inside the same file.
|
|
101
|
+
* Returns the literal or null.
|
|
102
|
+
*/
|
|
103
|
+
function resolveGradleVar(g, name, props) {
|
|
104
|
+
// `${project.springVersion}`, `${rootProject.ext.springVersion}`, `${ext.x}`
|
|
105
|
+
// all denote the same ext property.
|
|
106
|
+
name = name.replace(/^(?:rootProject|project)\.(?:ext\.)?|^ext\./, "");
|
|
107
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108
|
+
const m = g.match(new RegExp(`(?:^|[\\s{;])(?:val\\s+|def\\s+|ext\\.|project\\.ext\\.)?${esc}\\s*=\\s*['"]${VER}['"]`, "m"));
|
|
109
|
+
if (m) return m[1];
|
|
110
|
+
// Dotted access: `versions.spring` (Groovy map `versions = [spring: '…']`),
|
|
111
|
+
// `Versions.spring` (buildSrc `object Versions { const val spring = "…" }`),
|
|
112
|
+
// or a direct `versions.spring = '…'` assignment.
|
|
113
|
+
const dot = name.match(/^([\w]+)\.([\w]+)$/);
|
|
114
|
+
if (dot) {
|
|
115
|
+
const [, obj, key] = dot;
|
|
116
|
+
const eo = obj.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), ek = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
117
|
+
const map = g.match(new RegExp(`\\b${eo}\\s*=\\s*\\[([\\s\\S]*?)\\]`));
|
|
118
|
+
if (map) { const kv = map[1].match(new RegExp(`(?:^|[\\s,\\[])['"]?${ek}['"]?\\s*:\\s*['"]${VER}['"]`)); if (kv) return kv[1]; }
|
|
119
|
+
const objBlock = g.match(new RegExp(`object\\s+${eo}\\s*\\{([\\s\\S]*?)\\n\\}`));
|
|
120
|
+
if (objBlock) { const kv = objBlock[1].match(new RegExp(`\\bval\\s+${ek}\\s*(?::\\s*String)?\\s*=\\s*["']${VER}["']`)); if (kv) return kv[1]; }
|
|
121
|
+
const direct = g.match(new RegExp(`\\b${eo}\\.${ek}\\s*=\\s*['"]${VER}['"]`));
|
|
122
|
+
if (direct) return direct[1];
|
|
123
|
+
}
|
|
124
|
+
// Second source: gradle.properties (`springVersion=4.3.30.RELEASE`). Kotlin
|
|
125
|
+
// DSL `val springVersion: String by project` reads from there too.
|
|
126
|
+
if (props && typeof props[name] === "string" && new RegExp(`^${VER}$`).test(props[name].trim())) return props[name].trim();
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Parse gradle.properties into {key: value}. Comments and blanks skipped. */
|
|
131
|
+
function parseGradleProperties(text) {
|
|
132
|
+
const out = {};
|
|
133
|
+
if (!text) return out;
|
|
134
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
135
|
+
const line = raw.trim();
|
|
136
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
137
|
+
const m = line.match(/^([^=:\s]+)\s*[=:]\s*(.*)$/);
|
|
138
|
+
if (m) out[m[1]] = m[2];
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Turn a captured version-or-variable into a literal, or null. */
|
|
144
|
+
function literalOrResolve(g, captured, props) {
|
|
145
|
+
if (!captured) return null;
|
|
146
|
+
const v = captured.match(/^\$\{?([\w.]+)\}?$/);
|
|
147
|
+
if (v) return resolveGradleVar(g, v[1], props);
|
|
148
|
+
return /^\d/.test(captured) ? captured : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Files a build script pulls in for variable definitions:
|
|
153
|
+
* `apply from: 'gradle/dependencies.gradle'` / `apply(from = "…")`.
|
|
154
|
+
* Returns the relative paths as written.
|
|
155
|
+
*/
|
|
156
|
+
function gradleAppliedScripts(g) {
|
|
157
|
+
const out = [];
|
|
158
|
+
for (const m of g.matchAll(/apply\s*(?:\(\s*)?from\s*[:=]\s*['"]([^'"]+)['"]/g)) out.push(m[1]);
|
|
159
|
+
for (const m of g.matchAll(/apply\s+from\s*:\s*rootProject\.file\(\s*['"]([^'"]+)['"]\s*\)/g)) out.push(m[1]);
|
|
160
|
+
return [...new Set(out)];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Spring Framework version from a Gradle build file. Order:
|
|
165
|
+
* 1. spring-framework-bom coordinate (the one place a project pins it when
|
|
166
|
+
* individual deps are versionless)
|
|
167
|
+
* 2. explicit coordinate `org.springframework:spring-xxx:V`
|
|
168
|
+
* 3. group/name/version notation
|
|
169
|
+
* Each accepts a `${var}` and resolves it in-file.
|
|
170
|
+
*/
|
|
171
|
+
function gradleSpringFrameworkVersion(g, props) {
|
|
172
|
+
const CANDIDATES = [
|
|
173
|
+
new RegExp(`spring-framework-bom:(\\$\\{?[\\w.]+\\}?|${VER})`),
|
|
174
|
+
new RegExp(`['"]${SPRING_GROUP}:${SPRING_FW_ARTIFACT}:(\\$\\{?[\\w.]+\\}?|${VER})['"]`),
|
|
175
|
+
];
|
|
176
|
+
for (const re of CANDIDATES) {
|
|
177
|
+
const m = g.match(re);
|
|
178
|
+
if (!m) continue;
|
|
179
|
+
const v = literalOrResolve(g, m[1], props);
|
|
180
|
+
if (v) return v;
|
|
181
|
+
}
|
|
182
|
+
// group:/name:/version: map notation, any key order, single- or multi-line.
|
|
183
|
+
for (const d of gradleMapDeps(g)) {
|
|
184
|
+
if (!d.isSpringFw || !d.version) continue;
|
|
185
|
+
const v = literalOrResolve(g, d.version, props);
|
|
186
|
+
if (v) return v;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse Gradle map-notation dependencies: a run of `group:`/`name:`/`version:`
|
|
193
|
+
* pairs (2–3 of them, any order, commas/newlines between). Returns
|
|
194
|
+
* [{group, name, version, isSpringFw}].
|
|
195
|
+
*/
|
|
196
|
+
function gradleMapDeps(g) {
|
|
197
|
+
const out = [];
|
|
198
|
+
const RUN = /((?:\b(?:group|name|version)\s*:\s*['"][^'"]*['"]\s*,?\s*){2,3})/g;
|
|
199
|
+
for (const m of g.matchAll(RUN)) {
|
|
200
|
+
const d = {};
|
|
201
|
+
for (const kv of m[1].matchAll(/\b(group|name|version)\s*:\s*['"]([^'"]*)['"]/g)) d[kv[1]] = kv[2];
|
|
202
|
+
if (!d.group || !d.name) continue;
|
|
203
|
+
d.isSpringFw = new RegExp(`^${SPRING_GROUP}$`).test(d.group) && new RegExp(`^${SPRING_FW_ARTIFACT}$`).test(d.name);
|
|
204
|
+
out.push(d);
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Spring Boot version from a Gradle build file — covers the forms the
|
|
211
|
+
* existing detector already handled PLUS the Boot 1.x/2.x buildscript
|
|
212
|
+
* classpath form (`spring-boot-gradle-plugin:1.5.22.RELEASE`, with or
|
|
213
|
+
* without a `${springBootVersion}` indirection).
|
|
214
|
+
*/
|
|
215
|
+
function gradleSpringBootVersion(g, props) {
|
|
216
|
+
const CANDIDATES = [
|
|
217
|
+
new RegExp(`id\\s*\\(?\\s*['"]org\\.springframework\\.boot['"]\\s*\\)?\\s*version\\s*\\(?\\s*['"](\\$\\{?[\\w.]+\\}?|${VER})['"]`),
|
|
218
|
+
new RegExp(`spring-boot-gradle-plugin:(\\$\\{?[\\w.]+\\}?|${VER})`),
|
|
219
|
+
new RegExp(`spring-boot-dependencies:(\\$\\{?[\\w.]+\\}?|${VER})`),
|
|
220
|
+
new RegExp(`org\\.springframework\\.boot[^\\n]*version\\s*['"](\\$\\{?[\\w.]+\\}?|${VER})['"]`),
|
|
221
|
+
];
|
|
222
|
+
for (const re of CANDIDATES) {
|
|
223
|
+
const m = g.match(re);
|
|
224
|
+
if (!m) continue;
|
|
225
|
+
const v = literalOrResolve(g, m[1], props);
|
|
226
|
+
if (v) return v;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── Gradle version catalog (libs.versions.toml) ────────────────────
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Spring Framework version from a version catalog. Finds a library whose
|
|
235
|
+
* module is `org.springframework:spring-*` and resolves its
|
|
236
|
+
* `version.ref` / inline `version`. Returns null if absent.
|
|
237
|
+
*/
|
|
238
|
+
function catalogSpringFrameworkVersion(toml) {
|
|
239
|
+
if (!toml) return null;
|
|
240
|
+
const lib = toml.match(new RegExp(`module\\s*=\\s*["']org\\.springframework:${SPRING_FW_ARTIFACT}["'][^\\n]*`));
|
|
241
|
+
if (!lib) return null;
|
|
242
|
+
const line = lib[0];
|
|
243
|
+
const ref = line.match(/version\.ref\s*=\s*["']([\w.-]+)["']/);
|
|
244
|
+
if (ref) {
|
|
245
|
+
const def = toml.match(new RegExp(`^\\s*${ref[1].replace(/[.-]/g, "\\$&")}\\s*=\\s*["']${VER}["']`, "m"));
|
|
246
|
+
return def ? def[1] : null;
|
|
247
|
+
}
|
|
248
|
+
const inline = line.match(new RegExp(`version\\s*=\\s*["']${VER}["']`));
|
|
249
|
+
return inline ? inline[1] : null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function catalogHasSpringFramework(toml) {
|
|
253
|
+
return !!toml && new RegExp(`org\\.springframework:${SPRING_FW_ARTIFACT}["']`).test(toml);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ─── Maven ───────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
/** `<packaging>war</packaging>` at project level, or null when absent. */
|
|
259
|
+
function mavenPackaging(pom) {
|
|
260
|
+
const m = pom.match(/<packaging>\s*(jar|war|ear|pom|bundle)\s*<\/packaging>/);
|
|
261
|
+
return m ? m[1] : null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Resolve `${prop}` against `<prop>value</prop>` in the same pom. */
|
|
265
|
+
function resolveMavenProp(pom, name) {
|
|
266
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
267
|
+
const m = pom.match(new RegExp(`<${esc}>\\s*${VER}\\s*</${esc}>`));
|
|
268
|
+
return m ? m[1] : null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function literalOrResolveMaven(pom, captured) {
|
|
272
|
+
if (!captured) return null;
|
|
273
|
+
const v = captured.match(/^\$\{([\w.-]+)\}$/);
|
|
274
|
+
if (v) return resolveMavenProp(pom, v[1]);
|
|
275
|
+
return /^\d/.test(captured) ? captured : null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Every <dependency> block whose groupId is EXACTLY org.springframework.
|
|
280
|
+
* Returns [{artifactId, version|null}]. Works on comment-stripped text.
|
|
281
|
+
*/
|
|
282
|
+
function mavenSpringFrameworkDeps(pomClean) {
|
|
283
|
+
const out = [];
|
|
284
|
+
const blocks = pomClean.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g);
|
|
285
|
+
for (const b of blocks) {
|
|
286
|
+
const body = b[1];
|
|
287
|
+
if (!new RegExp(`<groupId>\\s*${SPRING_GROUP}\\s*</groupId>`).test(body)) continue;
|
|
288
|
+
const a = body.match(/<artifactId>\s*([\w.-]+)\s*<\/artifactId>/);
|
|
289
|
+
if (!a) continue;
|
|
290
|
+
if (!new RegExp(`^${SPRING_FW_ARTIFACT}$`).test(a[1])) continue;
|
|
291
|
+
const v = body.match(/<version>\s*([^<]+?)\s*<\/version>/);
|
|
292
|
+
out.push({ artifactId: a[1], version: v ? v[1] : null });
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function mavenHasSpringFramework(pomClean) {
|
|
298
|
+
return mavenSpringFrameworkDeps(pomClean).length > 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Spring Framework version from a pom. Order:
|
|
303
|
+
* 1. spring-framework-bom import (dependencyManagement)
|
|
304
|
+
* 2. first org.springframework dependency carrying a <version>
|
|
305
|
+
* 3. a conventional <spring.version> / <spring-framework.version> property
|
|
306
|
+
* `${prop}` values are resolved in-file.
|
|
307
|
+
*/
|
|
308
|
+
function mavenSpringFrameworkVersion(pom, pomClean) {
|
|
309
|
+
const deps = mavenSpringFrameworkDeps(pomClean);
|
|
310
|
+
const bom = deps.find(d => d.artifactId === "spring-framework-bom" && d.version);
|
|
311
|
+
if (bom) { const v = literalOrResolveMaven(pom, bom.version); if (v) return v; }
|
|
312
|
+
for (const d of deps) {
|
|
313
|
+
if (!d.version) continue;
|
|
314
|
+
const v = literalOrResolveMaven(pom, d.version);
|
|
315
|
+
if (v) return v;
|
|
316
|
+
}
|
|
317
|
+
for (const prop of ["spring.version", "spring-framework.version", "springframework.version", "org.springframework.version"]) {
|
|
318
|
+
const v = resolveMavenProp(pom, prop);
|
|
319
|
+
if (v) return v;
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Spring Boot version from a pom: starter-parent <version>, the
|
|
326
|
+
* spring-boot-dependencies BOM import, or a <spring-boot.version> property.
|
|
327
|
+
*/
|
|
328
|
+
function mavenSpringBootVersion(pom, pomClean) {
|
|
329
|
+
// Bounded to the <parent> block: a `(?:(?!</parent>)[\s\S])*?` scan cannot
|
|
330
|
+
// run past </parent> into a later <dependency> that happens to mention
|
|
331
|
+
// spring-boot-starter-parent.
|
|
332
|
+
const parentBlock = pomClean.match(/<parent>((?:(?!<\/parent>)[\s\S])*)<\/parent>/);
|
|
333
|
+
if (parentBlock && /<artifactId>\s*spring-boot-starter-parent\s*<\/artifactId>/.test(parentBlock[1])) {
|
|
334
|
+
const pv = parentBlock[1].match(/<version>\s*([^<]+?)\s*<\/version>/);
|
|
335
|
+
if (pv) { const v = literalOrResolveMaven(pom, pv[1]); if (v) return v; }
|
|
336
|
+
}
|
|
337
|
+
const bom = pomClean.match(/<artifactId>\s*spring-boot-dependencies\s*<\/artifactId>\s*<version>\s*([^<]+?)\s*<\/version>/);
|
|
338
|
+
if (bom) { const v = literalOrResolveMaven(pom, bom[1]); if (v) return v; }
|
|
339
|
+
const prop = pom.match(new RegExp(`<spring-boot[.\\w-]*version>\\s*${VER}\\s*<`));
|
|
340
|
+
if (prop) return prop[1];
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Java level from a legacy maven-compiler-plugin <configuration>
|
|
346
|
+
* (`<source>1.5</source>`), used when no <java.version> /
|
|
347
|
+
* <maven.compiler.source> property exists. Returns the raw token.
|
|
348
|
+
*/
|
|
349
|
+
function mavenCompilerPluginSource(pom) {
|
|
350
|
+
// Walk <plugin> blocks individually so a compiler plugin WITHOUT a
|
|
351
|
+
// <configuration> cannot borrow <source> from the next plugin's block.
|
|
352
|
+
for (const b of pom.matchAll(/<plugin>((?:(?!<\/plugin>)[\s\S])*)<\/plugin>/g)) {
|
|
353
|
+
if (!/<artifactId>\s*maven-compiler-plugin\s*<\/artifactId>/.test(b[1])) continue;
|
|
354
|
+
const s = b[1].match(/<(?:source|release|target)>\s*(\d+(?:\.\d+)?)\s*<\/(?:source|release|target)>/);
|
|
355
|
+
return s ? s[1] : null;
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ─── Ant / Eclipse / no build tool ───────────────────────────────────
|
|
361
|
+
|
|
362
|
+
/** `<javac … source="1.6">` → "1.6". */
|
|
363
|
+
function antJavacSource(buildXml) {
|
|
364
|
+
const m = buildXml.match(/<javac\b[^>]*\b(?:source|target)\s*=\s*["'](\d+(?:\.\d+)?)["']/);
|
|
365
|
+
return m ? m[1] : null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** `.classpath` JRE container → "1.7" / "17"; null if absent. */
|
|
369
|
+
function eclipseJreLevel(classpathXml) {
|
|
370
|
+
const m = classpathXml.match(/(?:JavaSE|J2SE|JRE)-(\d+(?:\.\d+)?)/)
|
|
371
|
+
// Custom VM names: `jdk1.6.0_45`, `jre1.8.0_202`, `jdk-17.0.2`, `jdk17`
|
|
372
|
+
|| classpathXml.match(/\/(?:jdk|jre)-?(1\.\d|\d{1,2})(?:[._]\d+)*["'/]/);
|
|
373
|
+
return m ? m[1] : null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** `.settings/org.eclipse.jdt.core.prefs` → compliance / source level. */
|
|
377
|
+
function eclipseJdtPrefsLevel(prefs) {
|
|
378
|
+
if (!prefs) return null;
|
|
379
|
+
const m = prefs.match(/org\.eclipse\.jdt\.core\.compiler\.(?:compliance|source)\s*=\s*(\d+(?:\.\d+)?)/);
|
|
380
|
+
return m ? m[1] : null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Jar basenames referenced by `.classpath` `kind="lib"` / `kind="var"` entries. */
|
|
384
|
+
function eclipseClasspathJars(classpathXml) {
|
|
385
|
+
if (!classpathXml) return [];
|
|
386
|
+
const out = [];
|
|
387
|
+
for (const m of classpathXml.matchAll(/<classpathentry\b[^>]*\bkind\s*=\s*["'](?:lib|var)["'][^>]*\bpath\s*=\s*["']([^"']+\.jar)["']/gi)) out.push(m[1]);
|
|
388
|
+
for (const m of classpathXml.matchAll(/<classpathentry\b[^>]*\bpath\s*=\s*["']([^"']+\.jar)["'][^>]*\bkind\s*=\s*["'](?:lib|var)["']/gi)) out.push(m[1]);
|
|
389
|
+
return [...new Set(out)];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** IntelliJ `.idea/misc.xml` → `languageLevel="JDK_1_7"` / `"JDK_17"`. */
|
|
393
|
+
function intellijLanguageLevel(miscXml) {
|
|
394
|
+
if (!miscXml) return null;
|
|
395
|
+
const m = miscXml.match(/languageLevel\s*=\s*["']JDK_(\d+)(?:_(\d+))?["']/);
|
|
396
|
+
if (!m) return null;
|
|
397
|
+
return m[2] ? `${m[1]}.${m[2]}` : m[1];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** NetBeans `nbproject/project.properties` → { level, jars[] }. */
|
|
401
|
+
function netbeansProject(props) {
|
|
402
|
+
if (!props) return { level: null, jars: [] };
|
|
403
|
+
const level = (props.match(/^\s*javac\.(?:source|target)\s*=\s*(\d+(?:\.\d+)?)/m) || [])[1] || null;
|
|
404
|
+
const jars = [...props.matchAll(/^\s*file\.reference\.([^=\s]+\.jar)\s*=/gm)].map(m => m[1]);
|
|
405
|
+
return { level, jars };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* `WEB-INF/web.xml` evidence: Spring MVC (`org.springframework.web.servlet.
|
|
410
|
+
* DispatcherServlet`, `ContextLoaderListener`), Struts, servlet spec version.
|
|
411
|
+
*/
|
|
412
|
+
function webXmlFacts(webXml) {
|
|
413
|
+
if (!webXml) return { spring: false, struts: null, servletVersion: null };
|
|
414
|
+
return {
|
|
415
|
+
spring: /org\.springframework\./.test(webXml),
|
|
416
|
+
struts: /org\.apache\.struts2\.|StrutsPrepareAndExecuteFilter/.test(webXml) ? "struts2"
|
|
417
|
+
: /org\.apache\.struts\.action\.ActionServlet/.test(webXml) ? "struts" : null,
|
|
418
|
+
servletVersion: (webXml.match(/<web-app\b[^>]*\bversion\s*=\s*["'](\d+\.\d+)["']/) || [])[1] || null,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Spring XSD schema versions in a Spring XML config —
|
|
424
|
+
* `…/spring-beans-3.0.xsd` → "3.0". Major.minor only; the lowest-fidelity
|
|
425
|
+
* version source, used only when nothing else pinned it. Returns the
|
|
426
|
+
* highest version seen (a 3.0 project may still reference a 2.5 schema for
|
|
427
|
+
* one namespace).
|
|
428
|
+
*/
|
|
429
|
+
function springXsdVersion(xml) {
|
|
430
|
+
if (!xml) return null;
|
|
431
|
+
let best = null;
|
|
432
|
+
for (const m of xml.matchAll(/springframework\.org\/schema\/[\w-]+\/spring-[\w-]+-(\d+\.\d+)\.xsd/g)) {
|
|
433
|
+
if (!best || parseFloat(m[1]) > parseFloat(best)) best = m[1];
|
|
434
|
+
}
|
|
435
|
+
return best;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** Struts / JSF tags from Maven or Gradle text: [{tag, version}] */
|
|
439
|
+
function legacyFrameworkTags(text) {
|
|
440
|
+
const tags = [];
|
|
441
|
+
const push = (tag, v) => { if (!tags.some(t => t.tag === tag)) tags.push({ tag, version: v || null }); };
|
|
442
|
+
// Maven blocks
|
|
443
|
+
for (const b of text.matchAll(/<dependency>((?:(?!<\/dependency>)[\s\S])*)<\/dependency>/g)) {
|
|
444
|
+
const g = (b[1].match(/<groupId>\s*([^<]+?)\s*<\/groupId>/) || [])[1];
|
|
445
|
+
const a = (b[1].match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/) || [])[1];
|
|
446
|
+
const v = (b[1].match(/<version>\s*([^<]+?)\s*<\/version>/) || [])[1];
|
|
447
|
+
if (!g || !a) continue;
|
|
448
|
+
if (/^(org\.apache\.)?struts$/.test(g) && /^(struts|struts-core)$/.test(a)) push("struts", v);
|
|
449
|
+
if (g === "org.apache.struts" && a === "struts2-core") push("struts2", v);
|
|
450
|
+
if (/^javax\.faces$|^jakarta\.faces$|^org\.glassfish$/.test(g) && /faces|jsf/.test(a)) push("jsf", v);
|
|
451
|
+
}
|
|
452
|
+
// Gradle coordinates
|
|
453
|
+
for (const m of text.matchAll(new RegExp(`['"]((?:org\\.apache\\.)?struts):(struts|struts-core|struts2-core):(${VER})['"]`, "g"))) push(m[2] === "struts2-core" ? "struts2" : "struts", m[3]);
|
|
454
|
+
for (const m of text.matchAll(new RegExp(`['"](?:javax|jakarta)\\.faces:[\\w.-]+:(${VER})['"]`, "g"))) push("jsf", m[1]);
|
|
455
|
+
return tags;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function eclipseHasJavaNature(projectXml) {
|
|
459
|
+
return /org\.eclipse\.jdt\.core\.javanature/.test(projectXml || "");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Classify a list of jar file names (basename only). Returns
|
|
464
|
+
* { springFramework: bool, springFrameworkVersion, springBoot: bool,
|
|
465
|
+
* springBootVersion }
|
|
466
|
+
* `spring-webmvc-3.0.5.RELEASE.jar` → 3.0.5.RELEASE; `spring.jar` →
|
|
467
|
+
* present, version null (Spring 2.0 era shipped unversioned names).
|
|
468
|
+
*/
|
|
469
|
+
function classifyJars(jarNames) {
|
|
470
|
+
const r = { springFramework: false, springFrameworkVersion: null, springBoot: false, springBootVersion: null, databases: [], orm: null };
|
|
471
|
+
const fwRe = new RegExp(`^${SPRING_FW_ARTIFACT}(?:-${VER})?\\.jar$`);
|
|
472
|
+
const bootRe = new RegExp(`^spring-boot(?:-[\\w-]+)?-${VER}\\.jar$`);
|
|
473
|
+
// JDBC driver jar names → DB. Values reuse stack-detector's vocabulary.
|
|
474
|
+
// Includes drivers common in Korean enterprise deployments (Tibero, Altibase,
|
|
475
|
+
// Cubrid) — legacy SI systems frequently ship them in WEB-INF/lib.
|
|
476
|
+
const DB_JARS = [
|
|
477
|
+
[/^ojdbc\d*/i, "oracle"], [/^postgresql-/i, "postgresql"], [/^mysql-connector/i, "mysql"],
|
|
478
|
+
[/^mariadb-java-client/i, "mariadb"], [/^h2-/i, "h2"], [/^sqlite-jdbc/i, "sqlite"],
|
|
479
|
+
[/^(mssql-jdbc|sqljdbc|jtds)/i, "mssql"], [/^(db2jcc|jcc-)/i, "db2"], [/^tibero/i, "tibero"],
|
|
480
|
+
[/^altibase/i, "altibase"], [/^cubrid/i, "cubrid"], [/^mongo(db)?-(java-)?driver/i, "mongodb"],
|
|
481
|
+
];
|
|
482
|
+
// ORM jar names. iBatis before MyBatis: `mybatis` is not a substring of
|
|
483
|
+
// `ibatis`, but keep explicit precedence for the same reason
|
|
484
|
+
// stack-detector's IBATIS_REGEX runs first.
|
|
485
|
+
const ORM_JARS = [
|
|
486
|
+
[/^ibatis/i, "ibatis"], [/^mybatis/i, "mybatis"], [/^hibernate/i, "jpa"],
|
|
487
|
+
[/^eclipselink/i, "jpa"], [/^openjpa/i, "jpa"], [/^jooq-/i, "jooq"],
|
|
488
|
+
];
|
|
489
|
+
for (const n of jarNames) {
|
|
490
|
+
const b = n.match(bootRe);
|
|
491
|
+
if (b) { r.springBoot = true; if (!r.springBootVersion) r.springBootVersion = b[1]; continue; }
|
|
492
|
+
const f = n.match(fwRe);
|
|
493
|
+
if (f) { r.springFramework = true; if (!r.springFrameworkVersion && f[1]) r.springFrameworkVersion = f[1]; }
|
|
494
|
+
for (const [re, db] of DB_JARS) if (re.test(n) && !r.databases.includes(db)) r.databases.push(db);
|
|
495
|
+
if (!r.orm) for (const [re, orm] of ORM_JARS) if (re.test(n)) { r.orm = orm; break; }
|
|
496
|
+
}
|
|
497
|
+
return r;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ─── Ant + Ivy ───────────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
/** `<dependency org="org.springframework" name="spring-webmvc" rev="V"/>` */
|
|
503
|
+
function ivySpringFrameworkVersion(ivy) {
|
|
504
|
+
if (!ivy) return null;
|
|
505
|
+
for (const m of ivy.matchAll(/<dependency\b([^>]*)\/?>/g)) {
|
|
506
|
+
const a = m[1];
|
|
507
|
+
if (!new RegExp(`\\borg\\s*=\\s*["']${SPRING_GROUP}["']`).test(a)) continue;
|
|
508
|
+
const name = (a.match(/\bname\s*=\s*["']([\w.-]+)["']/) || [])[1];
|
|
509
|
+
if (!name || !new RegExp(`^${SPRING_FW_ARTIFACT}$`).test(name)) continue;
|
|
510
|
+
const rev = (a.match(new RegExp(`\\brev\\s*=\\s*["']${VER}["']`)) || [])[1];
|
|
511
|
+
if (rev) return rev;
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
function ivyHasSpringFramework(ivy) {
|
|
516
|
+
return !!ivy && [...ivy.matchAll(/<dependency\b([^>]*)\/?>/g)].some(m =>
|
|
517
|
+
new RegExp(`\\borg\\s*=\\s*["']${SPRING_GROUP}["']`).test(m[1]) &&
|
|
518
|
+
new RegExp(`\\bname\\s*=\\s*["']${SPRING_FW_ARTIFACT}["']`).test(m[1]));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ─── eGovFrame (전자정부 표준프레임워크) ─────────────────────────────
|
|
522
|
+
// Korea's government-standard framework wraps Spring MVC. Group ids:
|
|
523
|
+
// `egovframework.rte` (RTE 2.x–4.x), `egovframework.rte.*`. Reported as
|
|
524
|
+
// `spring-framework` (templates and prompts understand Spring) plus an
|
|
525
|
+
// `egovframe <version>` tag in `detected`. Its poms pin Spring through
|
|
526
|
+
// `<spring.maven.version>`, which the Maven property list already resolves.
|
|
527
|
+
|
|
528
|
+
/** eGovFrame RTE version from a pom (comment-stripped) or a Gradle file. */
|
|
529
|
+
function egovframeVersion(text, propsText) {
|
|
530
|
+
if (!text) return null;
|
|
531
|
+
// Maven <dependency> with groupId egovframework.rte[.x]
|
|
532
|
+
for (const b of text.matchAll(/<dependency>((?:(?!<\/dependency>)[\s\S])*)<\/dependency>/g)) {
|
|
533
|
+
if (!/<groupId>\s*egovframework\.rte(?:\.[\w.]+)?\s*<\/groupId>/.test(b[1])) continue;
|
|
534
|
+
const v = (b[1].match(/<version>\s*([^<]+?)\s*<\/version>/) || [])[1];
|
|
535
|
+
if (!v) continue;
|
|
536
|
+
const lit = literalOrResolveMaven(propsText || text, v);
|
|
537
|
+
if (lit) return lit;
|
|
538
|
+
}
|
|
539
|
+
// Property fallback
|
|
540
|
+
const prop = resolveMavenProp(propsText || text, "egovframework.rte.version");
|
|
541
|
+
if (prop) return prop;
|
|
542
|
+
// Gradle coordinate
|
|
543
|
+
const g = text.match(new RegExp(`['"]egovframework\\.rte(?:\\.[\\w.]+)?:[\\w.-]+:(\\$\\{?[\\w.]+\\}?|${VER})['"]`));
|
|
544
|
+
if (g) return literalOrResolve(text, g[1]);
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
function hasEgovframe(text) {
|
|
548
|
+
return !!text && /egovframework\.rte/.test(text);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
module.exports = {
|
|
552
|
+
SPRING_GROUP, gradleMapDeps, gradleAppliedScripts, eclipseJdtPrefsLevel, eclipseClasspathJars,
|
|
553
|
+
intellijLanguageLevel, netbeansProject, webXmlFacts, springXsdVersion, legacyFrameworkTags,
|
|
554
|
+
parseGradleProperties, ivySpringFrameworkVersion, ivyHasSpringFramework, egovframeVersion, hasEgovframe,
|
|
555
|
+
VER,
|
|
556
|
+
gradleJvmPlugins, gradlePackaging, gradleIsJvm, gradleHasSpringFramework,
|
|
557
|
+
gradleSpringFrameworkVersion, gradleSpringBootVersion, resolveGradleVar,
|
|
558
|
+
catalogSpringFrameworkVersion, catalogHasSpringFramework,
|
|
559
|
+
mavenPackaging, mavenSpringFrameworkDeps, mavenHasSpringFramework,
|
|
560
|
+
mavenSpringFrameworkVersion, mavenSpringBootVersion, mavenCompilerPluginSource,
|
|
561
|
+
antJavacSource, eclipseJreLevel, eclipseHasJavaNature, classifyJars,
|
|
562
|
+
};
|