@orsetra/camel-pom-gen 1.0.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,221 @@
1
+ /**
2
+ * pom.xml serializer — mirrors pkg/util/maven/maven_project.go → MarshalBytes.
3
+ *
4
+ * Produces the same pom.xml structure as the Camel K operator:
5
+ * - properties (sourceEncoding, jar type, outputTimestamp)
6
+ * - dependencyManagement (BOMs)
7
+ * - dependencies
8
+ * - repositories / pluginRepositories
9
+ * - build/plugins (quarkus-maven-plugin)
10
+ * - profiles (JIB)
11
+ */
12
+ import type { MavenDependency, MavenPlugin, MavenProfile, MavenProject, MavenRepository } from '../dependencies/types'
13
+
14
+ const POM_NS = 'http://maven.apache.org/POM/4.0.0'
15
+ const XSI = 'http://www.w3.org/2001/XMLSchema-instance'
16
+ const XSD = 'http://maven.apache.org/xsd/maven-4.0.0.xsd'
17
+
18
+ // ── XML helpers ───────────────────────────────────────────────────────────────
19
+
20
+ function esc(s: string): string {
21
+ return s
22
+ .replace(/&/g, '&')
23
+ .replace(/</g, '&lt;')
24
+ .replace(/>/g, '&gt;')
25
+ .replace(/"/g, '&quot;')
26
+ }
27
+
28
+ function tag(name: string, value: string, indent: string): string {
29
+ return `${indent}<${name}>${esc(value)}</${name}>\n`
30
+ }
31
+
32
+ // ── Dependency block ──────────────────────────────────────────────────────────
33
+
34
+ function depBlock(dep: MavenDependency, indent: string, exclusions?: { groupId: string; artifactId: string }[]): string {
35
+ let s = `${indent}<dependency>\n`
36
+ s += tag('groupId', dep.groupId, indent + ' ')
37
+ s += tag('artifactId', dep.artifactId, indent + ' ')
38
+ if (dep.version) s += tag('version', dep.version, indent + ' ')
39
+ if (dep.scope) s += tag('scope', dep.scope, indent + ' ')
40
+ if (dep.type) s += tag('type', dep.type, indent + ' ')
41
+ if (dep.classifier) s += tag('classifier', dep.classifier, indent + ' ')
42
+ if (exclusions && exclusions.length > 0) {
43
+ s += `${indent} <exclusions>\n`
44
+ for (const ex of exclusions) {
45
+ s += `${indent} <exclusion>\n`
46
+ s += tag('groupId', ex.groupId, indent + ' ')
47
+ s += tag('artifactId', ex.artifactId, indent + ' ')
48
+ s += `${indent} </exclusion>\n`
49
+ }
50
+ s += `${indent} </exclusions>\n`
51
+ }
52
+ s += `${indent}</dependency>\n`
53
+ return s
54
+ }
55
+
56
+ // ── Repository block ──────────────────────────────────────────────────────────
57
+
58
+ function repoBlock(repo: MavenRepository, indent: string): string {
59
+ let s = `${indent}<repository>\n`
60
+ s += tag('id', repo.id, indent + ' ')
61
+ if (repo.name) s += tag('name', repo.name, indent + ' ')
62
+ s += tag('url', repo.url, indent + ' ')
63
+ if (repo.releases) {
64
+ s += `${indent} <releases>\n`
65
+ s += tag('enabled', String(repo.releases.enabled), indent + ' ')
66
+ if (repo.releases.checksumPolicy) s += tag('checksumPolicy', repo.releases.checksumPolicy, indent + ' ')
67
+ s += `${indent} </releases>\n`
68
+ }
69
+ if (repo.snapshots) {
70
+ s += `${indent} <snapshots>\n`
71
+ s += tag('enabled', String(repo.snapshots.enabled), indent + ' ')
72
+ if (repo.snapshots.checksumPolicy) s += tag('checksumPolicy', repo.snapshots.checksumPolicy, indent + ' ')
73
+ s += `${indent} </snapshots>\n`
74
+ }
75
+ s += `${indent}</repository>\n`
76
+ return s
77
+ }
78
+
79
+ // ── Plugin block ──────────────────────────────────────────────────────────────
80
+
81
+ function pluginBlock(plugin: MavenPlugin, indent: string): string {
82
+ let s = `${indent}<plugin>\n`
83
+ s += tag('groupId', plugin.groupId, indent + ' ')
84
+ s += tag('artifactId', plugin.artifactId, indent + ' ')
85
+ if (plugin.version) s += tag('version', plugin.version, indent + ' ')
86
+ if (plugin.extensions) s += `${indent} <extensions>true</extensions>\n`
87
+ if (plugin.executions && plugin.executions.length > 0) {
88
+ s += `${indent} <executions>\n`
89
+ for (const exec of plugin.executions) {
90
+ s += `${indent} <execution>\n`
91
+ if (exec.id) s += tag('id', exec.id, indent + ' ')
92
+ if (exec.phase) s += tag('phase', exec.phase, indent + ' ')
93
+ if (exec.goals.length > 0) {
94
+ s += `${indent} <goals>\n`
95
+ for (const goal of exec.goals) s += tag('goal', goal, indent + ' ')
96
+ s += `${indent} </goals>\n`
97
+ }
98
+ s += `${indent} </execution>\n`
99
+ }
100
+ s += `${indent} </executions>\n`
101
+ }
102
+ if (plugin.dependencies && plugin.dependencies.length > 0) {
103
+ s += `${indent} <dependencies>\n`
104
+ for (const dep of plugin.dependencies) s += depBlock(dep, indent + ' ')
105
+ s += `${indent} </dependencies>\n`
106
+ }
107
+ if (plugin.configurationXml) {
108
+ s += `${indent} <configuration>\n${plugin.configurationXml}\n${indent} </configuration>\n`
109
+ }
110
+ s += `${indent}</plugin>\n`
111
+ return s
112
+ }
113
+
114
+ // ── Profile block ─────────────────────────────────────────────────────────────
115
+
116
+ function profileBlock(profile: MavenProfile): string {
117
+ // If a raw XML string is provided, inject it verbatim (used for JIB profile)
118
+ if (profile.rawXml) return profile.rawXml + '\n'
119
+
120
+ let s = ` <profile>\n`
121
+ s += tag('id', profile.id, ' ')
122
+ if (typeof profile.activeByDefault === 'boolean') {
123
+ s += ` <activation>\n`
124
+ s += tag('activeByDefault', String(profile.activeByDefault), ' ')
125
+ s += ` </activation>\n`
126
+ }
127
+ if (profile.plugins && profile.plugins.length > 0) {
128
+ s += ` <build>\n`
129
+ s += ` <plugins>\n`
130
+ for (const p of profile.plugins) s += pluginBlock(p, ' ')
131
+ s += ` </plugins>\n`
132
+ s += ` </build>\n`
133
+ }
134
+ s += ` </profile>\n`
135
+ return s
136
+ }
137
+
138
+ // ── Main serializer ───────────────────────────────────────────────────────────
139
+
140
+ export function serializePom(project: MavenProject): string {
141
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>\n`
142
+ xml += `<project xmlns="${POM_NS}"\n`
143
+ xml += ` xmlns:xsi="${XSI}"\n`
144
+ xml += ` xsi:schemaLocation="${POM_NS} ${XSD}">\n`
145
+ xml += ` <modelVersion>4.0.0</modelVersion>\n\n`
146
+ xml += tag('groupId', project.groupId, ' ')
147
+ xml += tag('artifactId', project.artifactId, ' ')
148
+ xml += tag('version', project.version, ' ')
149
+ xml += '\n'
150
+
151
+ // ── properties ──────────────────────────────────────────────────────────
152
+ const props = Object.entries(project.properties)
153
+ if (props.length > 0) {
154
+ xml += ` <properties>\n`
155
+ for (const [k, v] of props) xml += tag(k, v, ' ')
156
+ xml += ` </properties>\n\n`
157
+ }
158
+
159
+ // ── dependencyManagement (BOMs) ─────────────────────────────────────────
160
+ if (project.boms.length > 0) {
161
+ xml += ` <dependencyManagement>\n <dependencies>\n`
162
+ for (const bom of project.boms) {
163
+ xml += ` <dependency>\n`
164
+ xml += tag('groupId', bom.groupId, ' ')
165
+ xml += tag('artifactId', bom.artifactId, ' ')
166
+ xml += tag('version', bom.version, ' ')
167
+ xml += tag('type', 'pom', ' ')
168
+ xml += tag('scope', 'import', ' ')
169
+ xml += ` </dependency>\n`
170
+ }
171
+ xml += ` </dependencies>\n </dependencyManagement>\n\n`
172
+ }
173
+
174
+ // ── dependencies ────────────────────────────────────────────────────────
175
+ if (project.dependencies.length > 0) {
176
+ xml += ` <dependencies>\n`
177
+ for (const dep of project.dependencies) {
178
+ const key = `${dep.groupId}:${dep.artifactId}`
179
+ xml += depBlock(dep, ' ', project.exclusions.get(key))
180
+ }
181
+ xml += ` </dependencies>\n\n`
182
+ }
183
+
184
+ // ── repositories ────────────────────────────────────────────────────────
185
+ if (project.repositories.length > 0) {
186
+ xml += ` <repositories>\n`
187
+ for (const r of project.repositories) xml += repoBlock(r, ' ')
188
+ xml += ` </repositories>\n\n`
189
+ }
190
+
191
+ // ── pluginRepositories (mirrors repositories) ────────────────────────────
192
+ const pluginRepos = project.pluginRepositories.length > 0
193
+ ? project.pluginRepositories
194
+ : project.repositories // operator copies repositories → pluginRepositories
195
+ if (pluginRepos.length > 0) {
196
+ xml += ` <pluginRepositories>\n`
197
+ for (const r of pluginRepos) {
198
+ // <pluginRepositories> uses <pluginRepository> element name
199
+ const block = repoBlock(r, ' ').replace('<repository>', '<pluginRepository>').replace('</repository>', '</pluginRepository>')
200
+ xml += block
201
+ }
202
+ xml += ` </pluginRepositories>\n\n`
203
+ }
204
+
205
+ // ── build / plugins ──────────────────────────────────────────────────────
206
+ if (project.build.plugins.length > 0) {
207
+ xml += ` <build>\n <plugins>\n`
208
+ for (const p of project.build.plugins) xml += pluginBlock(p, ' ')
209
+ xml += ` </plugins>\n </build>\n\n`
210
+ }
211
+
212
+ // ── profiles ─────────────────────────────────────────────────────────────
213
+ if (project.profiles.length > 0) {
214
+ xml += ` <profiles>\n`
215
+ for (const p of project.profiles) xml += profileBlock(p)
216
+ xml += ` </profiles>\n\n`
217
+ }
218
+
219
+ xml += `</project>\n`
220
+ return xml
221
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Legacy / low-level project builder.
3
+ *
4
+ * The primary API (generatePom) uses the template engine instead.
5
+ * This module remains for callers that need the full programmatic
6
+ * MavenProject model (e.g. unit tests, custom serialization).
7
+ */
8
+ import type { MavenProject } from '../dependencies/types'
9
+ import { createProject, addDependencyGAV } from '../dependencies/manager'
10
+ import type { CamelKRuntimeConfig } from '../config'
11
+ import { getQuarkusPluginVersion } from '../config'
12
+
13
+ const CAMEL_K_GROUP = 'org.apache.camel.k'
14
+ const QUARKUS_PLATFORM = 'io.quarkus.platform'
15
+ const QUARKUS_PLUGIN_GROUP = 'io.quarkus'
16
+
17
+ /** Raw JIB profile XML — same as XMLJibProfile in pkg/util/jib/configuration.go. */
18
+ export const JIB_PROFILE_XML = ` <profile>
19
+ <id>jib</id>
20
+ <activation>
21
+ <activeByDefault>false</activeByDefault>
22
+ </activation>
23
+ <build>
24
+ <plugins>
25
+ <plugin>
26
+ <groupId>com.google.cloud.tools</groupId>
27
+ <artifactId>jib-maven-plugin</artifactId>
28
+ <version>3.4.1</version>
29
+ <dependencies>
30
+ <dependency>
31
+ <groupId>com.google.cloud.tools</groupId>
32
+ <artifactId>jib-layer-filter-extension-maven</artifactId>
33
+ <version>0.3.0</version>
34
+ </dependency>
35
+ </dependencies>
36
+ <configuration>
37
+ <container>
38
+ <entrypoint>INHERIT</entrypoint>
39
+ <args>
40
+ <arg>jshell</arg>
41
+ </args>
42
+ </container>
43
+ <allowInsecureRegistries>true</allowInsecureRegistries>
44
+ <extraDirectories>
45
+ <paths>
46
+ <path>
47
+ <from>../context</from>
48
+ <into>/deployments</into>
49
+ </path>
50
+ </paths>
51
+ <permissions>
52
+ <permission>
53
+ <file>/deployments/*</file>
54
+ <mode>755</mode>
55
+ </permission>
56
+ </permissions>
57
+ </extraDirectories>
58
+ <pluginExtensions>
59
+ <pluginExtension>
60
+ <implementation>com.google.cloud.tools.jib.maven.extension.layerfilter.JibLayerFilterExtension</implementation>
61
+ <configuration implementation="com.google.cloud.tools.jib.maven.extension.layerfilter.Configuration">
62
+ <filters>
63
+ <Filter>
64
+ <glob>/app/**</glob>
65
+ </Filter>
66
+ </filters>
67
+ </configuration>
68
+ </pluginExtension>
69
+ </pluginExtensions>
70
+ </configuration>
71
+ </plugin>
72
+ </plugins>
73
+ </build>
74
+ </profile>`
75
+
76
+ /**
77
+ * Build a fully-hydrated MavenProject (for use with serializePom).
78
+ * The primary generatePom() uses templates instead — use this only when you
79
+ * need the project model for programmatic manipulation.
80
+ */
81
+ export function createQuarkusProject(
82
+ integrationArtifactId: string,
83
+ config: CamelKRuntimeConfig,
84
+ ): MavenProject {
85
+ const project = createProject(
86
+ 'org.apache.camel.k.integration',
87
+ integrationArtifactId,
88
+ config.operatorVersion,
89
+ )
90
+
91
+ project.properties['project.build.sourceEncoding'] = 'UTF-8'
92
+ project.properties['quarkus.package.jar.type'] = 'fast-jar'
93
+ project.properties['project.build.outputTimestamp'] = new Date().toISOString()
94
+
95
+ if (config.flavor === 'plain-quarkus') {
96
+ project.boms.push({ groupId: QUARKUS_PLATFORM, artifactId: 'quarkus-camel-bom', version: config.runtimeVersion })
97
+ project.boms.push({ groupId: QUARKUS_PLATFORM, artifactId: 'quarkus-bom', version: config.runtimeVersion })
98
+ } else {
99
+ project.boms.push({ groupId: CAMEL_K_GROUP, artifactId: 'camel-k-runtime-bom', version: config.runtimeVersion })
100
+ addDependencyGAV(project, CAMEL_K_GROUP, 'camel-k-runtime')
101
+ }
102
+
103
+ project.build.plugins.push({
104
+ groupId: QUARKUS_PLUGIN_GROUP,
105
+ artifactId: 'quarkus-maven-plugin',
106
+ version: getQuarkusPluginVersion(config),
107
+ extensions: true,
108
+ executions: [{ id: 'build-integration', goals: ['build'] }],
109
+ })
110
+
111
+ project.profiles.push({ id: 'jib', rawXml: JIB_PROFILE_XML })
112
+
113
+ return project
114
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Template engine for pom.xml generation.
3
+ *
4
+ * The template carries all static structural elements (properties, BOMs,
5
+ * quarkus-maven-plugin, JIB profile). The only dynamic section is
6
+ * {{dependencies}}, which is replaced on every generation.
7
+ *
8
+ * Supported tokens:
9
+ * {{operatorVersion}} — Camel K operator version (pom <version>)
10
+ * {{runtimeVersion}} — BOM version (quarkus platform or camel-k-runtime)
11
+ * {{quarkusPlatformVersion}} — quarkus-maven-plugin version (legacy quarkus only)
12
+ * {{outputTimestamp}} — ISO-8601 build timestamp (reproducible builds)
13
+ * {{dependencies}} — replaced with the full <dependencies> XML block
14
+ */
15
+ import type { CamelKRuntimeConfig } from '../config'
16
+ import { getQuarkusPluginVersion } from '../config'
17
+ import { PLAIN_QUARKUS_TEMPLATE } from '../templates/plain-quarkus'
18
+ import { QUARKUS_TEMPLATE } from '../templates/quarkus'
19
+ import type { MavenProject } from '../dependencies/types'
20
+
21
+ // ── Template registry ──────────────────────────────────────────────────────────
22
+
23
+ /** Return the bundled template for the given runtime flavor. */
24
+ export function getDefaultTemplate(flavor: CamelKRuntimeConfig['flavor']): string {
25
+ return flavor === 'plain-quarkus' ? PLAIN_QUARKUS_TEMPLATE : QUARKUS_TEMPLATE
26
+ }
27
+
28
+ // ── Token substitution ────────────────────────────────────────────────────────
29
+
30
+ export interface TemplateTokens {
31
+ operatorVersion: string
32
+ runtimeVersion: string
33
+ quarkusPlatformVersion: string
34
+ outputTimestamp: string
35
+ }
36
+
37
+ export function buildTokens(config: CamelKRuntimeConfig): TemplateTokens {
38
+ return {
39
+ operatorVersion: config.operatorVersion,
40
+ runtimeVersion: config.runtimeVersion,
41
+ quarkusPlatformVersion: getQuarkusPluginVersion(config),
42
+ outputTimestamp: new Date().toISOString(),
43
+ }
44
+ }
45
+
46
+ function replaceTokens(template: string, tokens: TemplateTokens): string {
47
+ return template
48
+ .replace(/\{\{operatorVersion\}\}/g, tokens.operatorVersion)
49
+ .replace(/\{\{runtimeVersion\}\}/g, tokens.runtimeVersion)
50
+ .replace(/\{\{quarkusPlatformVersion\}\}/g, tokens.quarkusPlatformVersion)
51
+ .replace(/\{\{outputTimestamp\}\}/g, tokens.outputTimestamp)
52
+ }
53
+
54
+ // ── Dependencies block serializer ─────────────────────────────────────────────
55
+
56
+ function esc(s: string): string {
57
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
58
+ }
59
+
60
+ function tag(name: string, value: string, indent: string): string {
61
+ return `${indent}<${name}>${esc(value)}</${name}>\n`
62
+ }
63
+
64
+ /**
65
+ * Serialize only the `<dependencies>...</dependencies>` block from the project.
66
+ * This is the fragment injected into `{{dependencies}}`.
67
+ */
68
+ export function serializeDepsBlock(project: MavenProject): string {
69
+ if (project.dependencies.length === 0) return ''
70
+
71
+ let xml = `<dependencies>\n`
72
+ for (const dep of project.dependencies) {
73
+ const key = `${dep.groupId}:${dep.artifactId}`
74
+ const exclusions = project.exclusions.get(key) ?? []
75
+
76
+ xml += ` <dependency>\n`
77
+ xml += tag('groupId', dep.groupId, ' ')
78
+ xml += tag('artifactId', dep.artifactId, ' ')
79
+ if (dep.version) xml += tag('version', dep.version, ' ')
80
+ if (dep.scope) xml += tag('scope', dep.scope, ' ')
81
+ if (dep.type) xml += tag('type', dep.type, ' ')
82
+ if (dep.classifier) xml += tag('classifier', dep.classifier, ' ')
83
+ if (exclusions.length > 0) {
84
+ xml += ` <exclusions>\n`
85
+ for (const ex of exclusions) {
86
+ xml += ` <exclusion>\n`
87
+ xml += tag('groupId', ex.groupId, ' ')
88
+ xml += tag('artifactId', ex.artifactId, ' ')
89
+ xml += ` </exclusion>\n`
90
+ }
91
+ xml += ` </exclusions>\n`
92
+ }
93
+ xml += ` </dependency>\n`
94
+ }
95
+ xml += ` </dependencies>`
96
+ return xml
97
+ }
98
+
99
+ // ── Main render function ──────────────────────────────────────────────────────
100
+
101
+ /**
102
+ * Render a pom.xml string by:
103
+ * 1. Substituting all `{{token}}` placeholders.
104
+ * 2. Replacing `{{dependencies}}` with the serialized <dependencies> block.
105
+ *
106
+ * If the template has no `{{dependencies}}` marker, the deps block is inserted
107
+ * before `</project>` as a fallback.
108
+ */
109
+ export function renderTemplate(
110
+ template: string,
111
+ tokens: TemplateTokens,
112
+ project: MavenProject,
113
+ ): string {
114
+ const depsBlock = serializeDepsBlock(project)
115
+ let result = replaceTokens(template, tokens)
116
+
117
+ if (result.includes('{{dependencies}}')) {
118
+ result = result.replace('{{dependencies}}', depsBlock)
119
+ } else {
120
+ // Fallback: insert before </project>
121
+ result = result.replace('</project>', ` ${depsBlock}\n\n</project>`)
122
+ }
123
+
124
+ return result
125
+ }
@@ -0,0 +1,25 @@
1
+ // Reserved for future global configuration options (e.g. FileSystemProvider).
2
+ export interface CamelPomGenConfig {}
3
+
4
+ let _config: CamelPomGenConfig = {}
5
+
6
+ /** Set package-level defaults (call once at IDE/app startup). */
7
+ export function configure(config: CamelPomGenConfig): void {
8
+ _config = { ..._config, ...config }
9
+ }
10
+
11
+ /** Read the current global config. */
12
+ export function getGlobalConfig(): Readonly<CamelPomGenConfig> {
13
+ return _config
14
+ }
15
+
16
+ /**
17
+ * Resolve the template to use.
18
+ * Currently returns the bundled default; reserved for future extension.
19
+ */
20
+ export function resolveTemplate(
21
+ _inline: string | undefined,
22
+ defaultTemplate: string,
23
+ ): string {
24
+ return defaultTemplate
25
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Base inspector — regex patterns and shared URI/dependency logic ported from
3
+ * pkg/util/source/inspector.go in camel-k.
4
+ */
5
+ import type { CamelCatalog } from '../catalog/types'
6
+ import {
7
+ decodeComponent,
8
+ getDependencyID,
9
+ getArtifactByDataFormat,
10
+ getLanguageDependency,
11
+ isResolvable,
12
+ getURIScheme,
13
+ isHTTPScheme,
14
+ isPassiveScheme,
15
+ } from '../catalog/catalog'
16
+ import type { InspectionResult } from './types'
17
+
18
+ // ─── Regex patterns (mirrors pkg/util/source/inspector.go) ───────────────────
19
+
20
+ export const RE_FROM = /from\s*\(\s*"([a-zA-Z0-9-]+:[^"]+)"/g
21
+ export const RE_FROM_F = /fromF\s*\(\s*"([a-zA-Z0-9-]+:[^"]+)"/g
22
+ export const RE_TO = /\.to\s*\(\s*"([a-zA-Z0-9-]+:[^"]+)"/g
23
+ export const RE_TO_D = /\.toD\s*\(\s*"([a-zA-Z0-9-]+:[^"]+)"/g
24
+ export const RE_TO_F = /\.toF\s*\(\s*"([a-zA-Z0-9-]+:[^"]+)"/g
25
+ export const RE_WIRE_TAP = /\.wireTap\s*\(\s*"([a-zA-Z0-9-]+:[^"]+)"/g
26
+ export const RE_LANGUAGE = /language\s*\(\s*["']([a-zA-Z0-9-]+[^"']+)["']\s*,.*\)/g
27
+ export const RE_CAMEL_TYPE = /.*(org\.apache\.camel.*(?:Component|DataFormat|Language))/g
28
+ export const RE_JSON_LIBRARY = /.*JsonLibrary\.Jackson.*/
29
+ export const RE_JSON_LANGUAGE = /.*\.json\(\).*/
30
+ export const RE_BEAN = /.*\.bean\(.*\).*/
31
+ export const RE_ERROR_HANDLER = /errorHandler\s*\(\s*deadLetterChannel\s*\(\s*["']([a-zA-Z0-9-]+:[^"']+)["']\s*\).*/g
32
+ export const RE_CIRCUIT_BREAKER = /.*\.circuitBreaker\(\).*/
33
+ export const RE_REST_CONFIG = /.*restConfiguration\(\).*/
34
+ export const RE_REST = /.*rest\s*\([^)]*\).*/
35
+ export const RE_REST_CLOSURE = /.*rest\s*\{\s*/
36
+ export const RE_OPEN_API = /.*\.openApi\s*\([^)]*\).*/
37
+ export const RE_GROOVY_LANG = /.*\.groovy\s*\(.*\).*/
38
+ export const RE_JSONPATH_LANG = /.*\.?(jsonpath|jsonpathWriteAsString)\s*\(.*\).*/
39
+ export const RE_OGNL = /.*\.ognl\s*\(.*\).*/
40
+ export const RE_MVEL = /.*\.mvel\s*\(.*\).*/
41
+ export const RE_XQUERY = /.*\.xquery\s*\(.*\).*/
42
+ export const RE_XPATH = /.*\.?xpath\s*\(.*\).*/
43
+ export const RE_XTOKENIZE = /.*\.xtokenize\s*\(.*\).*/
44
+ export const RE_KAMELET_EIP = /kamelet\s*\(\s*"(?:\/\/)?([a-z0-9-.]+(?:\/[a-z0-9-.]+)?)(?:$|[^a-z0-9-.].*)"/
45
+
46
+ const DEFAULT_JSON_DF = 'jackson'
47
+
48
+ // ─── Pattern → dependency resolver map ───────────────────────────────────────
49
+
50
+ type DepResolver = (catalog: CamelCatalog) => string[]
51
+
52
+ const PATTERN_DEP_RESOLVERS: Array<[RegExp, DepResolver]> = [
53
+ [RE_BEAN, (c) => {
54
+ const a = Object.values(c.artifacts).find(a => a.schemes?.some(s => s.id === 'bean'))
55
+ return a ? [getDependencyID(a)] : []
56
+ }],
57
+ [RE_JSON_LIBRARY, (c) => {
58
+ const a = getArtifactByDataFormat(c, DEFAULT_JSON_DF)
59
+ return a ? [getDependencyID(a)] : []
60
+ }],
61
+ [RE_JSON_LANGUAGE, (c) => {
62
+ const a = getArtifactByDataFormat(c, DEFAULT_JSON_DF)
63
+ return a ? [getDependencyID(a)] : []
64
+ }],
65
+ [RE_REST_CONFIG, (c) => capabilityDeps(c, 'rest')],
66
+ [RE_REST, (c) => capabilityDeps(c, 'rest')],
67
+ [RE_REST_CLOSURE,(c) => capabilityDeps(c, 'rest')],
68
+ [RE_OPEN_API, (c) => {
69
+ const a = Object.values(c.artifacts).find(a => a.schemes?.some(s => s.id === 'rest-openapi'))
70
+ return a ? [getDependencyID(a)] : []
71
+ }],
72
+ [RE_GROOVY_LANG, (c) => langDep(c, 'groovy')],
73
+ [RE_JSONPATH_LANG,(c) => langDep(c, 'jsonpath')],
74
+ [RE_OGNL, (c) => langDep(c, 'ognl')],
75
+ [RE_MVEL, (c) => langDep(c, 'mvel')],
76
+ [RE_XQUERY, (c) => langDep(c, 'xquery')],
77
+ [RE_XPATH, (c) => langDep(c, 'xpath')],
78
+ [RE_XTOKENIZE, (c) => langDep(c, 'xtokenize')],
79
+ ]
80
+
81
+ function capabilityDeps(catalog: CamelCatalog, cap: string): string[] {
82
+ const c = catalog.runtime.capabilities[cap]
83
+ return c?.dependencies.map(d => `mvn:${d.groupId}:${d.artifactId}`) ?? []
84
+ }
85
+
86
+ function langDep(catalog: CamelCatalog, lang: string): string[] {
87
+ const dep = getLanguageDependency(catalog, lang)
88
+ return dep ? [dep] : []
89
+ }
90
+
91
+ // ─── Shared inspection logic ──────────────────────────────────────────────────
92
+
93
+ export function createEmptyResult(): InspectionResult {
94
+ return { fromURIs: [], toURIs: [], dependencies: [], capabilities: [], exposesHTTP: false, passiveOnly: false }
95
+ }
96
+
97
+ /** Extract all regex-matched group[1] values from a string (resets lastIndex). */
98
+ export function matchAll(re: RegExp, text: string): string[] {
99
+ const results: string[] = []
100
+ re.lastIndex = 0
101
+ let m: RegExpExecArray | null
102
+ while ((m = re.exec(text)) !== null) {
103
+ results.push(m[1])
104
+ if (!re.global) break
105
+ }
106
+ re.lastIndex = 0
107
+ return results
108
+ }
109
+
110
+ /**
111
+ * Core analysis: given extracted URIs and full source text, resolve all
112
+ * dependencies and capabilities against the catalog.
113
+ */
114
+ export function analyzeWithCatalog(
115
+ catalog: CamelCatalog,
116
+ result: InspectionResult,
117
+ sourceText: string,
118
+ ): void {
119
+ const deps = new Set(result.dependencies)
120
+ const caps = new Set(result.capabilities)
121
+
122
+ // ── URI-based dependencies ─────────────────────────────────────────────────
123
+ for (const uri of [...result.fromURIs, ...result.toURIs]) {
124
+ if (!isResolvable(uri)) continue
125
+ const artifact = decodeComponent(catalog, uri)
126
+ if (!artifact) continue
127
+ deps.add(getDependencyID(artifact))
128
+ // transitive deps declared in the artifact
129
+ for (const dep of artifact.dependencies ?? []) {
130
+ deps.add(`mvn:${dep.groupId}:${dep.artifactId}`)
131
+ }
132
+ }
133
+
134
+ // ── platform-http capability ───────────────────────────────────────────────
135
+ for (const uri of result.fromURIs) {
136
+ if (getURIScheme(uri) === 'platform-http') caps.add('platform-http')
137
+ }
138
+
139
+ // ── Pattern-based deps (bean, xpath, rest, ...) ───────────────────────────
140
+ for (const [pattern, resolver] of PATTERN_DEP_RESOLVERS) {
141
+ if (pattern.test(sourceText)) {
142
+ for (const dep of resolver(catalog)) deps.add(dep)
143
+ if (pattern === RE_REST_CONFIG || pattern === RE_REST || pattern === RE_REST_CLOSURE) {
144
+ caps.add('rest')
145
+ }
146
+ }
147
+ }
148
+
149
+ // ── Circuit breaker capability ─────────────────────────────────────────────
150
+ if (RE_CIRCUIT_BREAKER.test(sourceText)) {
151
+ caps.add('circuit-breaker')
152
+ for (const dep of capabilityDeps(catalog, 'circuit-breaker')) deps.add(dep)
153
+ }
154
+
155
+ // ── language() DSL calls ──────────────────────────────────────────────────
156
+ for (const lang of matchAll(RE_LANGUAGE, sourceText)) {
157
+ const dep = getLanguageDependency(catalog, lang.trim())
158
+ if (dep) deps.add(dep)
159
+ }
160
+
161
+ // ── errorHandler(deadLetterChannel(...)) ──────────────────────────────────
162
+ for (const uri of matchAll(RE_ERROR_HANDLER, sourceText)) {
163
+ if (!isResolvable(uri)) continue
164
+ const artifact = decodeComponent(catalog, uri)
165
+ if (artifact) deps.add(getDependencyID(artifact))
166
+ }
167
+
168
+ // ── dataformat: URI ───────────────────────────────────────────────────────
169
+ for (const uri of [...result.fromURIs, ...result.toURIs]) {
170
+ if (!uri.startsWith('dataformat:')) continue
171
+ const parts = uri.split(':')
172
+ if (parts.length >= 3) {
173
+ const artifact = getArtifactByDataFormat(catalog, parts[1])
174
+ if (artifact) deps.add(getDependencyID(artifact))
175
+ }
176
+ }
177
+
178
+ result.dependencies = [...deps]
179
+ result.capabilities = [...caps]
180
+
181
+ // ── HTTP exposure & passive-only ──────────────────────────────────────────
182
+ result.exposesHTTP =
183
+ result.capabilities.includes('rest') ||
184
+ result.fromURIs.some(uri => isHTTPScheme(catalog, getURIScheme(uri)))
185
+
186
+ result.passiveOnly =
187
+ result.fromURIs.length > 0 &&
188
+ result.fromURIs.every(uri => {
189
+ if (uri === 'kamelet:source') return true
190
+ const scheme = getURIScheme(uri)
191
+ return isPassiveScheme(catalog, scheme) || isHTTPScheme(catalog, scheme)
192
+ })
193
+ }