This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new eaf2f35752bb CAMEL-24226: camel run/export - add --resource-dirs option
eaf2f35752bb is described below
commit eaf2f35752bb9718bf644612e175541666bef7f5
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 22 11:01:20 2026 +0200
CAMEL-24226: camel run/export - add --resource-dirs option
Add --resource-dirs option to camel run, camel export, and camel
kubernetes run/export commands. Recursively copies specified directories
into src/main/resources preserving the full directory structure. For
paths with '..' uses only the last directory name as target. Rejects
absolute paths and bare '..' for safety, limits to 1000 files per
directory. Multiple directories supported via comma separation or
repeated option.
Closes #24986
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../jbang/camel-jbang-configuration-metadata.json | 1 +
.../ROOT/pages/camel-jbang-configuration.adoc | 3 +-
.../modules/ROOT/pages/camel-jbang-projects.adoc | 34 +++++
.../modules/ROOT/pages/camel-jbang-running.adoc | 30 ++++
.../pages/jbang-commands/camel-jbang-debug.adoc | 1 +
.../camel-jbang-dependency-copy.adoc | 1 +
.../camel-jbang-dependency-list.adoc | 1 +
.../camel-jbang-dependency-update.adoc | 1 +
.../ROOT/pages/jbang-commands/camel-jbang-dev.adoc | 1 +
.../pages/jbang-commands/camel-jbang-export.adoc | 1 +
.../ROOT/pages/jbang-commands/camel-jbang-run.adoc | 1 +
.../pages/jbang-commands/camel-jbang-sbom.adoc | 1 +
.../META-INF/camel-jbang-commands-metadata.json | 12 +-
.../camel-jbang-configuration-metadata.json | 1 +
.../dsl/jbang/core/commands/ExportBaseCommand.java | 44 ++++++
.../apache/camel/dsl/jbang/core/commands/Run.java | 52 +++++++
.../dsl/jbang/core/common/CamelJBangConstants.java | 4 +
.../dsl/jbang/core/commands/ResourceDirsTest.java | 167 +++++++++++++++++++++
.../core/commands/kubernetes/KubernetesExport.java | 2 +
.../core/commands/kubernetes/KubernetesRun.java | 6 +
20 files changed, 357 insertions(+), 7 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
index c28faba036ef..38820baacb31 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/jbang/camel-jbang-configuration-metadata.json
@@ -45,6 +45,7 @@
{ "name": "camel.jbang.quarkusVersion", "required": false, "description":
"version of Quarkus Platform BOM", "label": "quarkus", "type": "string",
"javaType": "String", "secret": false },
{ "name": "camel.jbang.readmeFiles", "required": false, "description":
"README files included with the integration (Use commas to separate multiple
files)", "type": "string", "javaType": "String", "secret": false },
{ "name": "camel.jbang.repos", "required": false, "description":
"Additional Maven repositories for download on-demand (Use commas to separate
multiple repositories)", "label": "maven", "type": "string", "javaType":
"String", "secret": false },
+ { "name": "camel.jbang.resourceDirs", "required": false, "description":
"Additional resource directories to include recursively, preserving directory
structure (Use commas to separate multiple directories)", "type": "string",
"javaType": "String", "secret": false },
{ "name": "camel.jbang.runtime", "required": false, "description": "Which
runtime to use (camel-main, spring-boot, quarkus)", "type": "enum", "javaType":
"String", "secret": false, "enum": [ "camel-main", "spring-boot", "quarkus" ] },
{ "name": "camel.jbang.scriptFiles", "required": false, "description":
"Additional shell script files to export to src\/main\/scripts directory",
"type": "string", "javaType": "String", "secret": false },
{ "name": "camel.jbang.sourceDir", "required": false, "description":
"Source directory for dynamically loading Camel file(s) to run. When using
this, then files cannot be specified at the same time.", "label": "advanced",
"type": "string", "javaType": "String", "secret": false },
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
index 4a38e58f513a..f37467779487 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-configuration.adoc
@@ -4,7 +4,7 @@ This page covers configuring the Camel CLI — available options,
configuration
// jbang options: START
== Camel CLI configurations
-The camel.jbang supports 49 options, which are listed below.
+The camel.jbang supports 50 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -51,6 +51,7 @@ The camel.jbang supports 49 options, which are listed below.
| *camel.jbang.quarkusVersion* | version of Quarkus Platform BOM | | String
| *camel.jbang.readmeFiles* | README files included with the integration (Use
commas to separate multiple files) | | String
| *camel.jbang.repos* | Additional Maven repositories for download on-demand
(Use commas to separate multiple repositories) | | String
+| *camel.jbang.resourceDirs* | Additional resource directories to include
recursively, preserving directory structure (Use commas to separate multiple
directories) | | String
| *camel.jbang.runtime* | Which runtime to use (camel-main, spring-boot,
quarkus) | | String
| *camel.jbang.scriptFiles* | Additional shell script files to export to
src/main/scripts directory | | String
| *camel.jbang.sourceDir* | Source directory for dynamically loading Camel
file(s) to run. When using this, then files cannot be specified at the same
time. | | String
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
index 26b621316a9b..05e53e7039e5 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
@@ -103,6 +103,40 @@ camel export --runtime=quarkus
--gav=com.foo:acme:1.0-SNAPSHOT --dep=camel:manag
camel export --runtime=quarkus --gav=com.foo:acme:1.0-SNAPSHOT
--dep=camel:cli-connector
----
+=== Including resource directories
+
+To include entire directories of resource files (such as XSD schemas, WSDL
files, etc.)
+in the exported project while preserving their directory structure, use
`--resource-dirs`:
+
+[source,bash]
+----
+camel export --resource-dirs=soap
+----
+
+This recursively copies all files from the `soap/` directory into
`src/main/resources/soap/`,
+preserving the directory structure. For example, `soap/schemas/common.xsd`
becomes
+`src/main/resources/soap/schemas/common.xsd`.
+
+Multiple directories can be specified with commas:
+
+[source,bash]
+----
+camel export --resource-dirs=soap,wsdl
+----
+
+You can also reference directories outside the current project using relative
paths.
+When using `..` paths, only the last directory name is used as the target:
+
+[source,bash]
+----
+camel export --resource-dirs=../shared/schemas
+----
+
+This copies the contents into `src/main/resources/schemas/`.
+
+NOTE: Absolute paths (e.g., `/some/path`) are not allowed.
+The directory is limited to at most 1000 files as a safety measure.
+
=== Troubleshooting exporting
Use `--verbose` and `--logging` for more detail, or `--logging-level=DEBUG`
for full output.
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
index a80d34ef1ac6..4d56d7565df9 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
@@ -152,6 +152,36 @@ Multiple dependencies can be separated by comma:
camel run foo.java --dep=camel-saxon,com.foo:acme:1.0
----
+== Including resource directories
+
+To include entire directories of resource files (such as XSD schemas, WSDL
files, etc.)
+on the classpath while preserving their directory structure, use
`--resource-dirs`:
+
+[source,bash]
+----
+camel run route.yaml --resource-dirs=soap
+----
+
+This recursively adds all files from the `soap/` directory to the classpath,
+preserving the directory structure (e.g., `soap/schemas/common.xsd` is
accessible as `classpath:soap/schemas/common.xsd`).
+
+Multiple directories can be specified with commas:
+
+[source,bash]
+----
+camel run route.yaml --resource-dirs=soap,wsdl
+----
+
+You can also reference directories outside the current project using relative
paths:
+
+[source,bash]
+----
+camel run route.yaml --resource-dirs=../shared/schemas
+----
+
+NOTE: Absolute paths (e.g., `/some/path`) are not allowed.
+The directory is limited to at most 1000 files as a safety measure.
+
== Using 3rd-party Maven repositories
By default, Camel CLI downloads from the local Maven repository and Maven
Central.
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
index 2874c142b913..0f9265a5fec2 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-debug.adoc
@@ -83,6 +83,7 @@ camel debug [options]
| `--reload,--dev` | Enables dev mode (live reload when source files are
updated and saved) | | boolean
| `--remote-attach` | Attaches debugger remotely to an existing running Camel
integration. (Add camel-cli-debug JAR to the existing Camel application and run
before attaching this debugger) | | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include on the classpath (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | camel-main |
RuntimeType
| `--show-body` | Show message body in debug messages | true | boolean
| `--show-exception` | Show exception and stacktrace for failed messages |
true | boolean
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-copy.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-copy.adoc
index d8c90d2e84ef..2149196f292e 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-copy.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-copy.adoc
@@ -65,6 +65,7 @@ camel dependency copy [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--quiet` | Will be quiet, only print when error occurs | false | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include in src/main/resources (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType
| `--skip-plugins` | Skip plugins during export | false | boolean
| `--spring-boot-version` | Spring Boot version |
RuntimeType.SPRING_BOOT_VERSION | String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-list.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-list.adoc
index 575369f1c32b..a87b96fed253 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-list.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-list.adoc
@@ -64,6 +64,7 @@ camel dependency list [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--quiet` | Will be quiet, only print when error occurs | false | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include in src/main/resources (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType
| `--skip-plugins` | Skip plugins during export | false | boolean
| `--spring-boot-version` | Spring Boot version |
RuntimeType.SPRING_BOOT_VERSION | String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
index f5310726df1c..bc3ff5357e69 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dependency-update.adoc
@@ -65,6 +65,7 @@ camel dependency update [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--quiet` | Will be quiet, only print when error occurs | false | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include in src/main/resources (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType
| `--scan-routes` | Sync dependencies from route definitions. Only manages
org.apache.camel dependencies, preserving non-Camel dependencies. Removes
unused Camel dependencies. | | boolean
| `--skip-plugins` | Skip plugins during export | false | boolean
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
index 69218ec9f4a0..1b69782e934c 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-dev.adoc
@@ -76,6 +76,7 @@ camel dev [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--reload,--dev` | Enables dev mode (live reload when source files are
updated and saved) | | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include on the classpath (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | camel-main |
RuntimeType
| `--skip-plugins` | Skip resolving plugin dependencies | false | boolean
| `--source-dir` | Source directory for dynamically loading Camel file(s) to
run. When using this, then files cannot be specified at the same time. | |
String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-export.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-export.adoc
index b78a1b2349f6..12278c201622 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-export.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-export.adoc
@@ -63,6 +63,7 @@ camel export [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--quiet` | Will be quiet, only print when error occurs | false | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include in src/main/resources (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType
| `--skip-plugins` | Skip plugins during export | false | boolean
| `--spring-boot-version` | Spring Boot version |
RuntimeType.SPRING_BOOT_VERSION | String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
index ef505512255b..93c8bcfc962b 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-run.adoc
@@ -76,6 +76,7 @@ camel run [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--reload,--dev` | Enables dev mode (live reload when source files are
updated and saved) | | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include on the classpath (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | camel-main |
RuntimeType
| `--skip-plugins` | Skip resolving plugin dependencies | false | boolean
| `--source-dir` | Source directory for dynamically loading Camel file(s) to
run. When using this, then files cannot be specified at the same time. | |
String
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-sbom.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-sbom.adoc
index 06036a020a35..d0540d8eabbd 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-sbom.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-sbom.adoc
@@ -66,6 +66,7 @@ camel sbom [options]
| `--quarkus-version` | version of Quarkus Platform BOM; the default value is
looked up in Quarkus Extension Registry | | String
| `--quiet` | Will be quiet, only print when error occurs | false | boolean
| `--repo,--repos` | Additional maven repositories for download on-demand (Use
commas to separate multiple repositories) | | String
+| `--resource-dir,--resource-dirs` | Additional resource directories to
include in src/main/resources (recursive, preserving structure) | | List
| `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType
| `--sbom-format` | The SBOM format, possible values are cyclonedx or spdx |
CYCLONEDX_FORMAT | String
| `--sbom-output-format` | The SBOM output format, possible values are json or
xml | SBOM_JSON_FORMAT | String
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 7f66b663a036..85a31044c196 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -6,15 +6,15 @@
{ "name": "cmd", "fullName": "cmd", "description": "Performs commands in
the running Camel integrations, such as start\/stop route, or change logging
levels.", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"browse", "fullName": "cmd browse", "description": "Browse pending messages on
endpoints [...]
{ "name": "completion", "fullName": "completion", "description": "Generate
completion script for bash\/zsh", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "config", "fullName": "config", "description": "Get and set user
configuration values", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get",
"fullName": "config get", "description": "Display user configuration value",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
- { "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
- { "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
- { "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, [...]
+ { "name": "debug", "fullName": "debug", "description": "Debug local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug",
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names":
"--background", "description": "Run in the background", "defaultValue":
"false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To [...]
+ { "name": "dependency", "fullName": "dependency", "description": "Displays
all Camel dependencies required to run", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.DependencyCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name":
"copy", "fullName": "dependency copy", "description": "Copies all Camel
dependencies required to run to a specific directory", "sourc [...]
+ { "name": "dev", "fullName": "dev", "description": "Run in dev mode with
live reload", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Dev",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, [...]
{ "name": "dirty", "fullName": "dirty", "description": "Check if there are
dirty files from previous Camel runs that did not terminate gracefully",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Dirty",
"options": [ { "names": "--clean", "description": "Clean dirty files which are
no longer in use", "defaultValue": "false", "javaType": "boolean", "type":
"boolean" }, { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", " [...]
{ "name": "doc", "fullName": "doc", "description": "Shows documentation
for kamelet, component, and other Camel resources", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogDoc", "options": [ {
"names": "--camel-version", "description": "To use a different Camel version
than the default version", "javaType": "java.lang.String", "type": "string" },
{ "names": "--download", "description": "Whether to allow automatic downloading
JAR dependencies (over the internet [...]
{ "name": "doctor", "fullName": "doctor", "description": "Checks the
environment and reports potential issues", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Doctor", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ] },
{ "name": "eval", "fullName": "eval", "description": "Evaluate Camel
expressions and scripts", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression",
"fullName": "eval expression", "description": "Evaluates Camel expression",
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
{ "name": "explain", "fullName": "explain", "description": "Explain what a
Camel route does using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
ANTHROPIC_API_KEY, OPENAI_API_KEY, or LLM_API_KEY env vars", "javaType":
"java.lang.String", "type": "string" }, { "names": "--api-type", "description":
"API type: 'ollama', 'openai' (OpenAI-compatible), or 'anthropic' (A [...]
- { "name": "export", "fullName": "export", "description": "Export to other
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names":
"--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "ty [...]
+ { "name": "export", "fullName": "export", "description": "Export to other
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names":
"--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "ty [...]
{ "name": "get", "fullName": "get", "description": "Get status of Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ {
"names": "--watch", "description": "Execute periodically and showing output
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity",
"fullName": " [...]
{ "name": "harden", "fullName": "harden", "description": "Suggest security
hardening for Camel routes using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String",
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama'
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
{ "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio
web console", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ {
"names": "--host", "description": "Hostname to bind the Hawtio web console to",
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string"
}, { "names": "--openUrl", "description": "To automatic open Hawtio web console
in the web browser", "defaultValue": "true", "javaType": "boolean", "type":
[...]
@@ -26,8 +26,8 @@
{ "name": "plugin", "fullName": "plugin", "description": "Manage plugins
that add sub-commands to this CLI", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ {
"names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "add",
"fullName": "plugin add", "description": "Add new plugin", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginA [...]
{ "name": "ps", "fullName": "ps", "description": "List running Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.ListProcess", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "--pid", "description": "List only
pid in the output", "javaType": "boolean", "type": "boolean" }, { "names":
"--remote", "description": "Break down counters into remote\/total pairs",
"javaType": [...]
{ "name": "restart", "fullName": "restart", "description": "Restarts
running Camel integrations (stop + re-launch)", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.RestartProcess", "options": [
{ "names": "-h,--help", "description": "Display the help and sub-commands",
"javaType": "boolean", "type": "boolean" } ] },
- { "name": "run", "fullName": "run", "description": "Run as local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, { [...]
- { "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX
or SPDX SBOM for a specific project", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ {
"names": "--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "type" [...]
+ { "name": "run", "fullName": "run", "description": "Run as local Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run",
"options": [ { "names": "--background", "description": "Run in the background",
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names":
"--background-wait", "description": "To wait for run in background to startup
successfully, before returning", "defaultValue": "true", "javaType": "boolean",
"type": "boolean" }, { [...]
+ { "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX
or SPDX SBOM for a specific project", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ {
"names": "--build-property", "description": "Maven build properties, ex.
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, {
"names": "--camel-spring-boot-version", "description": "Camel version to use
with Spring Boot", "javaType": "java.lang.String", "type" [...]
{ "name": "script", "fullName": "script", "description": "Run Camel
integration as shell script for terminal scripting", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Script", "options": [ { "names":
"--logging", "description": "Can be used to turn on logging (logs to file in
<user home>\/.camel directory)", "defaultValue": "false", "javaType":
"boolean", "type": "boolean" }, { "names": "--logging-level", "description":
"Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "d [...]
{ "name": "shell", "fullName": "shell", "description": "Interactive Camel
CLI shell.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Shell",
"options": [ { "names": "-h,--help", "description": "Display the help and
sub-commands", "javaType": "boolean", "type": "boolean" } ] },
{ "name": "stop", "fullName": "stop", "description": "Shuts down running
Camel integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.StopProcess", "options": [ {
"names": "--kill", "description": "To force killing the process (SIGKILL)",
"javaType": "boolean", "type": "boolean" }, { "names": "-h,--help",
"description": "Display the help and sub-commands", "javaType": "boolean",
"type": "boolean" } ] },
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
index c28faba036ef..38820baacb31 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-configuration-metadata.json
@@ -45,6 +45,7 @@
{ "name": "camel.jbang.quarkusVersion", "required": false, "description":
"version of Quarkus Platform BOM", "label": "quarkus", "type": "string",
"javaType": "String", "secret": false },
{ "name": "camel.jbang.readmeFiles", "required": false, "description":
"README files included with the integration (Use commas to separate multiple
files)", "type": "string", "javaType": "String", "secret": false },
{ "name": "camel.jbang.repos", "required": false, "description":
"Additional Maven repositories for download on-demand (Use commas to separate
multiple repositories)", "label": "maven", "type": "string", "javaType":
"String", "secret": false },
+ { "name": "camel.jbang.resourceDirs", "required": false, "description":
"Additional resource directories to include recursively, preserving directory
structure (Use commas to separate multiple directories)", "type": "string",
"javaType": "String", "secret": false },
{ "name": "camel.jbang.runtime", "required": false, "description": "Which
runtime to use (camel-main, spring-boot, quarkus)", "type": "enum", "javaType":
"String", "secret": false, "enum": [ "camel-main", "spring-boot", "quarkus" ] },
{ "name": "camel.jbang.scriptFiles", "required": false, "description":
"Additional shell script files to export to src\/main\/scripts directory",
"type": "string", "javaType": "String", "secret": false },
{ "name": "camel.jbang.sourceDir", "required": false, "description":
"Source directory for dynamically loading Camel file(s) to run. When using
this, then files cannot be specified at the same time.", "label": "advanced",
"type": "string", "javaType": "String", "secret": false },
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index 664a2c603a27..dc2304e28fcc 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -248,6 +248,11 @@ public abstract class ExportBaseCommand extends
CamelCommand {
description = "Whether to automatic package scan JARs
for custom Spring or Quarkus beans making them available for Camel CLI")
protected boolean packageScanJars;
+ @CommandLine.Option(names = { "--resource-dir", "--resource-dirs" },
+ description = "Additional resource directories to
include in src/main/resources (recursive, preserving structure)",
+ split = ",")
+ protected List<String> resourceDirs = new ArrayList<>();
+
@CommandLine.Option(names = { "--build-property" },
description = "Maven build properties, ex.
--build-property=prop1=foo")
protected List<String> buildProperties = new ArrayList<>();
@@ -574,6 +579,7 @@ public abstract class ExportBaseCommand extends
CamelCommand {
run.kameletsVersion = kameletsVersion;
run.javaVersion = javaVersion;
run.localKameletDir = localKameletDir;
+ run.resourceDirs = resourceDirs;
run.ignoreLoadingError = ignoreLoadingError;
run.lazyBean = lazyBean;
run.property = applicationProperties;
@@ -896,6 +902,10 @@ public abstract class ExportBaseCommand extends
CamelCommand {
} else if (web) {
targetDir =
srcResourcesDir.resolve("META-INF/resources");
} else {
+ // skip files that belong to a resource dir (they are
copied with structure preserved later)
+ if (isUnderResourceDir(f)) {
+ continue;
+ }
targetDir = srcResourcesDir;
}
Files.createDirectories(targetDir);
@@ -980,6 +990,8 @@ public abstract class ExportBaseCommand extends
CamelCommand {
}
}
+ copyResourceDirs(srcResourcesDir);
+
if (!skipPlugins) {
Set<PluginExporter> exporters =
PluginHelper.getActivePlugins(getMain(), mavenResolver.repos()).values()
.stream()
@@ -995,6 +1007,38 @@ public abstract class ExportBaseCommand extends
CamelCommand {
}
}
+ private void copyResourceDirs(Path srcResourcesDir) throws Exception {
+ if (resourceDirs == null || resourceDirs.isEmpty()) {
+ return;
+ }
+ for (String dir : resourceDirs) {
+ Path dirPath = Run.validateResourceDir(dir);
+ // use the last component as the target directory name
+ // so ../shared/schemas -> src/main/resources/schemas/
+ Path baseName = dirPath.getFileName();
+ for (Path source : Run.walkResourceDir(dirPath)) {
+ Path relativePath = dirPath.relativize(source);
+ Path target =
srcResourcesDir.resolve(baseName).resolve(relativePath);
+ Files.createDirectories(target.getParent());
+ Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ }
+
+ private boolean isUnderResourceDir(String file) {
+ if (resourceDirs == null || resourceDirs.isEmpty()) {
+ return false;
+ }
+ Path filePath = Paths.get(file).normalize();
+ for (String dir : resourceDirs) {
+ Path dirPath = Paths.get(dir).normalize();
+ if (filePath.startsWith(dirPath)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
protected void adjustJavaSourceFileLine(String line, OutputStream fos)
throws Exception {
// noop
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index e0e261bfb7f5..dc4a6a06d73b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -180,6 +180,11 @@ public class Run extends CamelCommand {
description = "Source directory for dynamically loading Camel
file(s) to run. When using this, then files cannot be specified at the same
time.")
String sourceDir;
+ @Option(names = { "--resource-dir", "--resource-dirs" },
+ description = "Additional resource directories to include on the
classpath (recursive, preserving structure)",
+ split = ",")
+ List<String> resourceDirs = new ArrayList<>();
+
@Option(names = { "--background" }, defaultValue = "false", description =
"Run in the background")
public boolean background;
@@ -971,6 +976,16 @@ public class Run extends CamelCommand {
}
}
+ // walk resource directories and add files to classpath preserving
directory structure
+ if (resourceDirs != null) {
+ for (String dir : resourceDirs) {
+ Path dirPath = validateResourceDir(dir);
+ for (Path f : walkResourceDir(dirPath)) {
+ sjClasspathFiles.add(f.toString());
+ }
+ }
+ }
+
for (String file : files) {
if (file.startsWith("clipboard") &&
!(Files.exists(Paths.get(file)))) {
file = loadFromClipboard(file);
@@ -1130,6 +1145,13 @@ public class Run extends CamelCommand {
} else {
writeSetting(main, profileProperties, JKUBE_FILES, () -> null);
}
+ if (resourceDirs != null && !resourceDirs.isEmpty()) {
+ String dirs = String.join(",", resourceDirs);
+ main.addInitialProperty(RESOURCE_DIRS, dirs);
+ writeSettings(RESOURCE_DIRS, dirs);
+ } else {
+ writeSetting(main, profileProperties, RESOURCE_DIRS, () -> null);
+ }
if (sjKamelets.length() > 0) {
String loc =
main.getInitialProperties().getProperty("camel.component.kamelet.location");
@@ -2669,6 +2691,36 @@ public class Run extends CamelCommand {
return on.toLowerCase(Locale.ROOT).startsWith("readme");
}
+ static final int MAX_RESOURCE_DIR_FILES = 1000;
+
+ static Path validateResourceDir(String dir) {
+ Path path = Paths.get(dir).normalize();
+ if (path.isAbsolute()) {
+ throw new IllegalArgumentException("--resource-dirs only accepts
relative paths: " + dir);
+ }
+ if (path.getFileName() != null &&
path.getFileName().toString().equals("..")) {
+ throw new IllegalArgumentException(
+ "--resource-dirs path must end with a named directory, not
'..': " + dir);
+ }
+ if (!Files.isDirectory(path)) {
+ throw new IllegalArgumentException("--resource-dirs path is not a
directory: " + dir);
+ }
+ return path;
+ }
+
+ static List<Path> walkResourceDir(Path dirPath) throws IOException {
+ try (Stream<Path> walk = Files.walk(dirPath)) {
+ List<Path> files = walk.filter(Files::isRegularFile).toList();
+ if (files.size() > MAX_RESOURCE_DIR_FILES) {
+ throw new IllegalArgumentException(
+ "--resource-dirs directory contains " + files.size()
+ + " files which exceeds the
limit of " + MAX_RESOURCE_DIR_FILES + ": "
+ + dirPath);
+ }
+ return files;
+ }
+ }
+
private void writeSettings(String key, String value) {
try {
// use java.util.Properties to ensure the value is escaped
correctly
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
index 403761e5997f..526867409907 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelJBangConstants.java
@@ -60,6 +60,10 @@ public final class CamelJBangConstants {
javaType = "String", label = "kubernetes")
public static final String JKUBE_FILES = "camel.jbang.jkubeFiles";
+ @Metadata(description = "Additional resource directories to include
recursively, preserving directory structure (Use commas to separate multiple
directories)",
+ javaType = "String")
+ public static final String RESOURCE_DIRS = "camel.jbang.resourceDirs";
+
@Metadata(description = "Which runtime to use (camel-main, spring-boot,
quarkus)",
javaType = "String", enums = "camel-main,spring-boot,quarkus")
public static final String RUNTIME = "camel.jbang.runtime";
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ResourceDirsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ResourceDirsTest.java
new file mode 100644
index 000000000000..3de78f3fec1c
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ResourceDirsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ResourceDirsTest {
+
+ @Test
+ void validateShouldRejectAbsolutePath() {
+ assertThatThrownBy(() ->
Run.validateResourceDir("/some/absolute/path"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("only accepts relative paths");
+ }
+
+ @Test
+ void validateShouldRejectNonDirectory(@TempDir Path tempDir) throws
Exception {
+ // create a regular file inside tempDir, then use a relative path to it
+ Path file = tempDir.resolve("notadir.txt");
+ Files.writeString(file, "hello");
+
+ // use relative path from cwd to the file
+ Path relative = Path.of("").toAbsolutePath().relativize(file);
+ assertThatThrownBy(() -> Run.validateResourceDir(relative.toString()))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("not a directory");
+ }
+
+ @Test
+ void validateShouldAcceptRelativeDir(@TempDir Path tempDir) throws
Exception {
+ Path dir = tempDir.resolve("soap");
+ Files.createDirectories(dir);
+
+ // use relative path from cwd to the dir
+ Path relative = Path.of("").toAbsolutePath().relativize(dir);
+ Path result = Run.validateResourceDir(relative.toString());
+ assertThat(result).isNotNull();
+ }
+
+ @Test
+ void validateShouldAcceptDotDotPaths(@TempDir Path tempDir) throws
Exception {
+ // create project/child and shared dirs, use ../shared from child's
perspective
+ Path project = tempDir.resolve("project/child");
+ Path shared = tempDir.resolve("shared");
+ Files.createDirectories(project);
+ Files.createDirectories(shared);
+
+ // use relative path from cwd — this will contain .. segments
+ Path relative = Path.of("").toAbsolutePath().relativize(shared);
+ Path result = Run.validateResourceDir(relative.toString());
+ assertThat(result).isNotNull();
+ }
+
+ @Test
+ void walkShouldFindFilesRecursively(@TempDir Path tempDir) throws
Exception {
+ Path dir = tempDir.resolve("soap");
+ Path subDir = tempDir.resolve("soap/schemas");
+ Files.createDirectories(subDir);
+ Files.writeString(dir.resolve("file.xsd"), "<xsd/>");
+ Files.writeString(subDir.resolve("common.xsd"), "<xsd/>");
+
+ List<Path> files = Run.walkResourceDir(dir);
+ assertThat(files).hasSize(2);
+
assertThat(files).extracting(Path::getFileName).extracting(Path::toString)
+ .containsExactlyInAnyOrder("file.xsd", "common.xsd");
+ }
+
+ @Test
+ void walkShouldEnforceMaxFileLimit(@TempDir Path tempDir) throws Exception
{
+ Path dir = tempDir.resolve("big");
+ Files.createDirectories(dir);
+
+ for (int i = 0; i < Run.MAX_RESOURCE_DIR_FILES + 1; i++) {
+ Files.writeString(dir.resolve("file" + i + ".txt"), "content");
+ }
+
+ assertThatThrownBy(() -> Run.walkResourceDir(dir))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("exceeds the limit");
+ }
+
+ @Test
+ void parentPathShouldMapToLastComponent(@TempDir Path tempDir) throws
Exception {
+ // simulate ../shared/schemas structure where only the last dir name
is used as target
+ Path shared = tempDir.resolve("shared/schemas");
+ Files.createDirectories(shared);
+ Files.writeString(shared.resolve("common.xsd"), "<xsd/>");
+
+ Path dirPath = shared;
+ Path baseName = dirPath.getFileName();
+ assertThat(baseName.toString()).isEqualTo("schemas");
+
+ // verify target path uses only the last component
+ Path srcResources = tempDir.resolve("export/src/main/resources");
+ Files.createDirectories(srcResources);
+
+ for (Path source : Run.walkResourceDir(dirPath)) {
+ Path relativePath = dirPath.relativize(source);
+ Path target = srcResources.resolve(baseName).resolve(relativePath);
+ Files.createDirectories(target.getParent());
+ Files.copy(source, target);
+ }
+
+ // ../shared/schemas/common.xsd ->
src/main/resources/schemas/common.xsd
+ assertThat(srcResources.resolve("schemas/common.xsd")).exists();
+ }
+
+ @Test
+ void validateShouldRejectBareDotDot(@TempDir Path tempDir) {
+ assertThatThrownBy(() -> Run.validateResourceDir(".."))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("must end with a named directory");
+ }
+
+ @Test
+ void nestedParentPathShouldMapToLastComponent() {
+ // ../../mydata/foo -> last component is "foo"
+ Path path = Path.of("../../mydata/foo").normalize();
+ assertThat(path.getFileName().toString()).isEqualTo("foo");
+ }
+
+ @Test
+ void deepSubdirectoryShouldPreserveStructure(@TempDir Path tempDir) throws
Exception {
+ // create a deep structure: soap/types/common/base.xsd
+ Path dir = tempDir.resolve("soap");
+ Path deep = tempDir.resolve("soap/types/common");
+ Files.createDirectories(deep);
+ Files.writeString(dir.resolve("service.wsdl"), "<wsdl/>");
+ Files.writeString(deep.resolve("base.xsd"), "<xsd/>");
+
+ Path baseName = dir.getFileName();
+ Path srcResources = tempDir.resolve("export/src/main/resources");
+ Files.createDirectories(srcResources);
+
+ for (Path source : Run.walkResourceDir(dir)) {
+ Path relativePath = dir.relativize(source);
+ Path target = srcResources.resolve(baseName).resolve(relativePath);
+ Files.createDirectories(target.getParent());
+ Files.copy(source, target);
+ }
+
+ assertThat(srcResources.resolve("soap/service.wsdl")).exists();
+
assertThat(srcResources.resolve("soap/types/common/base.xsd")).exists();
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
index 8baf5bc56aa1..57243c453ba6 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
@@ -185,6 +185,7 @@ public class KubernetesExport extends Export {
logging = configurer.logging;
loggingLevel = configurer.loggingLevel;
verbose = configurer.verbose;
+ resourceDirs = configurer.resourceDirs;
observe = true; // always include observability-services for kubernetes
}
@@ -662,6 +663,7 @@ public class KubernetesExport extends Export {
String loggingLevel,
boolean verbose,
boolean skipPlugins,
+ List<String> resourceDirs,
String camelQuarkusGroupId,
String camelQuarkusArtifactId,
String camelQuarkusVersion) implements QuarkusPlatformMixinSpec,
MavenResolverMixinSpec {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
index 80b0ab78749b..d4aad60ce30c 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
@@ -207,6 +207,11 @@ public class KubernetesRun extends KubernetesBaseCommand {
@CommandLine.Option(names = { "--exclude" }, description = "Exclude files
by name or pattern")
List<String> excludes = new ArrayList<>();
+ @CommandLine.Option(names = { "--resource-dir", "--resource-dirs" },
+ description = "Additional resource directories to
include in src/main/resources (recursive, preserving structure)",
+ split = ",")
+ List<String> resourceDirs = new ArrayList<>();
+
@CommandLine.Option(names = { "--package-scan-jars" }, defaultValue =
"false",
description = "Whether to automatic package scan JARs
for custom Spring or Quarkus beans making them available for Camel CLI")
boolean packageScanJars;
@@ -482,6 +487,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
"info",
verbose,
skipPlugins,
+ resourceDirs,
quarkusPlatformBoms.quarkusCamelBom().getGroupId(),
quarkusPlatformBoms.quarkusCamelBom().getArtifactId(),
quarkusPlatformBoms.quarkusCamelBom().getVersion());