This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-23688-init-force in repository https://gitbox.apache.org/repos/asf/camel.git
commit c37dbf66bebe8de38e81a9e6bc9086dac35045b3 Author: Claus Ibsen <[email protected]> AuthorDate: Sat Oct 17 08:08:58 2026 +0200 CAMEL-23688: camel-jbang - camel init does not overwrite a file without --force camel init overwrote the file it creates when it existed already, such as a route someone had been working on. Now it exits with 1 and leaves the file as it is, and the new --force option overwrites it. The files camel init downloads from GitHub or a gist are checked first, and none is written when one of them exists. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 6 +++ .../pages/jbang-commands/camel-jbang-init.adoc | 1 + .../META-INF/camel-jbang-commands-metadata.json | 2 +- .../apache/camel/dsl/jbang/core/commands/Init.java | 54 +++++++++++++++++----- .../camel/dsl/jbang/core/commands/InitTest.java | 26 +++++++++++ 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 81cf732576ea..9f928ac56dca 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -587,6 +587,12 @@ the integrations unsorted. A row without the value of the key now comes first, i unknown `--type` exits with `2` (it showed nothing), a `--filter` that matches nothing says so on stderr, and `--json` prints the Kamelets as JSON, as the other `camel catalog` commands do. +=== camel-jbang - camel init does not overwrite a file + +`camel init` exits with `1` when the file it creates exists already, and leaves the file as it is: it overwrote it. +Use `--force` to overwrite it. The same holds for the files `camel init` downloads from GitHub or a gist, of which it +writes none when one of them exists. + === camel-jbang - camel get history * `camel get history --diagram` and `--it` exit with `2` when several integrations have a message history, and ask for diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-init.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-init.adoc index f9792e234294..939d243f86a2 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-init.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-init.adoc @@ -21,6 +21,7 @@ camel init [options] | Option | Description | Default | Type | `--clean-dir,--clean-directory` | Whether to clean directory first (deletes all files in directory) | | boolean | `--dir,--directory` | Directory relative path where the new Camel integration will be saved | . | String +| `--force` | Overwrite a file that exists already (by default the command fails instead) | | boolean | `--from-kamelet` | To be used when extending an existing Kamelet | | String | `--kamelets-version` | Apache Camel Kamelets version (auto-detected from classpath if not set) | | String | `--list` | List available templates | | boolean 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 ee4d49be23bb..bec964991348 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 @@ -19,7 +19,7 @@ { "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": "--open-url,--openUrl", "description": "To automatic open Hawtio web console in the web browser (without an interactive terminal the default [...] { "name": "infra", "fullName": "infra", "description": "List and Run external services for testing and prototyping", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "infra [...] - { "name": "init", "fullName": "init", "description": "Creates a new Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Init", "options": [ { "names": "--clean-dir,--clean-directory", "description": "Whether to clean directory first (deletes all files in directory)", "javaType": "boolean", "type": "boolean" }, { "names": "--dir,--directory", "description": "Directory relative path where the new Camel integration will be saved", "defaultValue": ".", "javaType" [...] + { "name": "init", "fullName": "init", "description": "Creates a new Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Init", "options": [ { "names": "--clean-dir,--clean-directory", "description": "Whether to clean directory first (deletes all files in directory)", "javaType": "boolean", "type": "boolean" }, { "names": "--dir,--directory", "description": "Directory relative path where the new Camel integration will be saved", "defaultValue": ".", "javaType" [...] { "name": "jolokia", "fullName": "jolokia", "description": "Attach Jolokia JVM Agent to a running Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Jolokia", "options": [ { "names": "--port", "description": "To use a specific port number when attaching Jolokia JVM Agent (default a free port is found in range 8778-9999)", "javaType": "int", "type": "integer" }, { "names": "--stop", "description": "Stops the Jolokia JVM Agent in the running Camel inte [...] { "name": "log", "fullName": "log", "description": "Tail logs from running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelLogAction", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--find", "description": "Find and highlight matching text (ignore case).", "javaType": "j [...] { "name": "nano", "fullName": "nano", "description": "Nano editor to edit file", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Nano", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java index 8bb3caf32a0c..4c8a6bf589db 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Init.java @@ -81,6 +81,10 @@ public class Init extends CamelCommand { description = "Whether to clean directory first (deletes all files in directory)") private boolean cleanDirectory; + @Option(names = { "--force" }, + description = "Overwrite a file that exists already (by default the command fails instead)") + private boolean force; + @Option(names = { "--from-kamelet" }, description = "To be used when extending an existing Kamelet") private String fromKamelet; @@ -232,10 +236,42 @@ public class Init extends CamelCommand { Files.createDirectories(parentPath); } } + if (existsAlready(targetPath)) { + return CliExitCode.FAILURE; + } Files.writeString(targetPath, content); return 0; } + /** + * Whether the file is there already and not to be overwritten (without --force), which is then an error. + */ + private boolean existsAlready(Path targetPath) { + if (!force && Files.exists(targetPath)) { + printer().printErr("File already exists: " + targetPath + " (use --force to overwrite it)"); + return true; + } + return false; + } + + /** + * Writes the downloaded files, and none of them when one of them is there already (without --force). + */ + private int writeDownloaded(List<Resource> resources) throws Exception { + for (Resource resource : resources) { + if (existsAlready(Paths.get(directory, FileUtil.stripPath(resource.getLocation())))) { + return CliExitCode.FAILURE; + } + } + for (Resource resource : resources) { + Path targetPath = Paths.get(directory, FileUtil.stripPath(resource.getLocation())); + try (OutputStream os = Files.newOutputStream(targetPath)) { + IOUtils.copy(resource.getInputStream(), os); + } + } + return 0; + } + /** * @return The package declaration lines to insert at the beginning of the file or empty string if no package found */ @@ -435,18 +471,15 @@ public class Init extends CamelCommand { CamelContext tiny = new DefaultCamelContext(); try (GitHubResourceResolver resolver = new GitHubResourceResolver()) { resolver.setCamelContext(tiny); + List<Resource> resources = new ArrayList<>(); for (String u : all.toString().split(",")) { Resource resource = resolver.resolve(u); if (!resource.exists()) { throw new ResourceDoesNotExist(resource); } - String loc = resource.getLocation(); - String name = FileUtil.stripPath(loc); - Path targetPath = Paths.get(directory, name); - try (OutputStream os = Files.newOutputStream(targetPath)) { - IOUtils.copy(resource.getInputStream(), os); - } + resources.add(resource); } + return writeDownloaded(resources); } } @@ -472,18 +505,15 @@ public class Init extends CamelCommand { CamelContext tiny = new DefaultCamelContext(); try (GistResourceResolver resolver = new GistResourceResolver()) { resolver.setCamelContext(tiny); + List<Resource> resources = new ArrayList<>(); for (String u : all.toString().split(",")) { Resource resource = resolver.resolve(u); if (!resource.exists()) { throw new ResourceDoesNotExist(resource); } - String loc = resource.getLocation(); - String name = FileUtil.stripPath(loc); - Path targetPath = Paths.get(directory, name); - try (OutputStream os = Files.newOutputStream(targetPath)) { - IOUtils.copy(resource.getInputStream(), os); - } + resources.add(resource); } + return writeDownloaded(resources); } } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java index 5ae9694efb97..b08b92803548 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/InitTest.java @@ -196,6 +196,32 @@ class InitTest { assertTrue(Files.readString(workingDir.resolve("my.yaml")).contains("kind: Pipe")); } + @Test + void initFailsWhenTheFileExists() throws Exception { + Path existing = Files.writeString(workingDir.resolve("my.camel.yaml"), "- route: {}"); + StringPrinter printer = new StringPrinter(); + Init initCommand = new Init(new CamelJBangMain().withPrinter(printer)); + CommandLine.populateCommand(initCommand, "my.camel.yaml", "--dir=" + workingDir); + + int exit = initCommand.doCall(); + + assertEquals(1, exit); + assertEquals("ERROR: File already exists: " + existing + " (use --force to overwrite it)", printer.getStderr()); + assertEquals("- route: {}", Files.readString(existing), "the file is kept as it was"); + } + + @Test + void initForceOverwritesTheFile() throws Exception { + Path existing = Files.writeString(workingDir.resolve("my.camel.yaml"), "- route: {}"); + Init initCommand = new Init(new CamelJBangMain()); + CommandLine.populateCommand(initCommand, "my.camel.yaml", "--dir=" + workingDir, "--force"); + + int exit = initCommand.doCall(); + + assertEquals(0, exit); + assertTrue(Files.readString(existing).contains("from:"), "the file is the template now"); + } + @Test void initCleansTheDirectoryFirst() throws Exception { Path dir = Files.createDirectories(workingDir.resolve("project"));
