This is an automated email from the ASF dual-hosted git repository.

mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new d3287536e9 Issue #9089 : Add local run configurations to new Hop 
setups (#9090)
d3287536e9 is described below

commit d3287536e91b4c032e231c26017cf6511f1b6373
Author: Matt Casters <[email protected]>
AuthorDate: Fri Oct 23 11:12:14 2026 +0200

    Issue #9089 : Add local run configurations to new Hop setups (#9090)
    
    * Issue #9089 : Add local run configurations to new Hop setups
    
    Setup copied only the top level of the default project template, so a new
    configuration was created without the local pipeline and workflow run
    configurations. Copy the full template and add those configurations when
    they are missing.
    
    * Issue #9089 : Copy nested folders only for the default project template
---
 .../modules/ROOT/pages/hop-tools/hop-setup.adoc    |   2 +-
 .../hop/setup/persist/ConfigFolderSeeder.java      |   3 +-
 .../org/apache/hop/setup/persist/HopVfsFiles.java  |  18 +-
 .../setup/persist/LocalRunConfigurationSeeder.java | 230 +++++++++++++++++++++
 .../setup/persist/ProjectRegistrationWriter.java   |  18 +-
 .../setup/gui/messages/messages_en_US.properties   |   2 +-
 .../hop/setup/persist/ConfigFolderSeederTest.java  |  14 ++
 .../persist/LocalRunConfigurationSeederTest.java   | 107 ++++++++++
 .../persist/ProjectRegistrationWriterTest.java     |  84 +++++++-
 9 files changed, 471 insertions(+), 7 deletions(-)

diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-setup.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-setup.adoc
index d8a9ebb7b9..85fbf9b394 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-setup.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-tools/hop-setup.adoc
@@ -86,7 +86,7 @@ 
image::hop-tools/setup/06-configure-hop-environment-dialog-projects-tab.png[Conf
 ** Windows: `%USERPROFILE%\Documents\Hop\default`
 * Register the *samples* project from this Hop installation 
(`<install>/config/projects/samples`) so the examples stay available after the 
config folder moves.
 
-Both options are selected by default. Apply writes them into the new 
`hop-config.json` (and creates `project-config.json` plus the install 
default-project template when the default home is empty).
+Both options are selected by default. Apply writes them into the new 
`hop-config.json`. An empty default project home is created from the install 
default-project template. That project includes a `local` pipeline run 
configuration and a `local` workflow run configuration. If the template does 
not contain them, setup adds them, together with the local execution info 
location and data profile they use.
 
 === Preview and apply
 
diff --git 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ConfigFolderSeeder.java
 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ConfigFolderSeeder.java
index 15124e9e4c..fde50e0266 100644
--- 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ConfigFolderSeeder.java
+++ 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ConfigFolderSeeder.java
@@ -67,7 +67,8 @@ public class ConfigFolderSeeder {
       result.addMessage("Would copy " + source + " to " + 
spec.getConfigFolder());
       return;
     }
-    HopVfsFiles.copyTree(source, spec.getConfigFolder());
+    // One level only: nested projects/default and projects/samples stay in 
the install tree.
+    HopVfsFiles.copyChildren(source, spec.getConfigFolder());
     result.addMessage("Copied install config from " + source + " to " + 
spec.getConfigFolder());
   }
 
diff --git 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/HopVfsFiles.java
 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/HopVfsFiles.java
index 1eb951dcdc..123a0dfb44 100644
--- 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/HopVfsFiles.java
+++ 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/HopVfsFiles.java
@@ -21,6 +21,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSelector;
 import org.apache.commons.vfs2.Selectors;
 import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.setup.HopSetupException;
@@ -72,14 +73,29 @@ public final class HopVfsFiles {
     }
   }
 
+  /**
+   * Copies the direct children of {@code sourcePath} into {@code 
destinationPath}. Child folders
+   * are created without their contents.
+   */
+  public static void copyChildren(String sourcePath, String destinationPath)
+      throws HopSetupException {
+    copySelected(sourcePath, destinationPath, Selectors.SELECT_CHILDREN);
+  }
+
+  /** Copies every file and subfolder under {@code sourcePath} into {@code 
destinationPath}. */
   public static void copyTree(String sourcePath, String destinationPath) 
throws HopSetupException {
+    copySelected(sourcePath, destinationPath, Selectors.EXCLUDE_SELF);
+  }
+
+  private static void copySelected(String sourcePath, String destinationPath, 
FileSelector selector)
+      throws HopSetupException {
     try {
       FileObject source = HopVfs.getFileObject(sourcePath);
       FileObject destination = HopVfs.getFileObject(destinationPath);
       if (!destination.exists()) {
         destination.createFolder();
       }
-      destination.copyFrom(source, Selectors.SELECT_CHILDREN);
+      destination.copyFrom(source, selector);
     } catch (Exception e) {
       throw new HopSetupException(
           "Unable to copy '" + sourcePath + "' to '" + destinationPath + "'", 
e);
diff --git 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/LocalRunConfigurationSeeder.java
 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/LocalRunConfigurationSeeder.java
new file mode 100644
index 0000000000..e8c0ec1d76
--- /dev/null
+++ 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/LocalRunConfigurationSeeder.java
@@ -0,0 +1,230 @@
+/*
+ * 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.hop.setup.persist;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.setup.HopEnvironmentApplyResult;
+import org.apache.hop.setup.HopSetupException;
+
+/**
+ * Adds the standard {@code local} pipeline and workflow run configurations to 
a new project.
+ *
+ * <p>Files already present are left unchanged. A file that exists in the 
install default-project
+ * template is copied from there. Otherwise the stock default-project content 
is written, including
+ * the local execution info location and data profile those run configurations 
use.
+ */
+public class LocalRunConfigurationSeeder {
+
+  static final String PIPELINE_RELATIVE = 
"metadata/pipeline-run-configuration/local.json";
+  static final String WORKFLOW_RELATIVE = 
"metadata/workflow-run-configuration/local.json";
+  static final String EXECUTION_INFO_RELATIVE = 
"metadata/execution-info-location/local-audit.json";
+  static final String DATA_PROFILE_RELATIVE = 
"metadata/execution-data-profile/first-last.json";
+
+  static final String PIPELINE_JSON =
+      """
+      {
+        "engineRunConfiguration": {
+          "Local": {
+            "feedback_size": "50000",
+            "sample_size": "100",
+            "sample_type_in_gui": "Last",
+            "rowset_size": "10000",
+            "safe_mode": false,
+            "show_feedback": false,
+            "topo_sort": false,
+            "gather_metrics": false
+          }
+        },
+        "name": "local",
+        "configurationVariables": [],
+        "description": "",
+        "executionInfoLocationName": "local-audit",
+        "dataProfile": "first-last",
+        "defaultSelection": true
+      }
+      """;
+
+  static final String WORKFLOW_JSON =
+      """
+      {
+        "engineRunConfiguration": {
+          "Local": {
+            "safe_mode": false
+          }
+        },
+        "name": "local",
+        "description": "",
+        "executionInfoLocationName": "local-audit",
+        "defaultSelection": true
+      }
+      """;
+
+  static final String EXECUTION_INFO_JSON =
+      """
+      {
+        "executionInfoLocation": {
+          "local-folder": {
+            "pluginName": "File location",
+            "pluginId": "local-folder",
+            "rootFolder": "${HOP_AUDIT_FOLDER}/executions/"
+          }
+        },
+        "dataLoggingDelay": "2000",
+        "name": "local-audit",
+        "description": "",
+        "dataLoggingInterval": "5000"
+      }
+      """;
+
+  static final String DATA_PROFILE_JSON =
+      """
+      {
+        "name": "first-last",
+        "description": "capture the first and last 100 rows of every Hop 
transform",
+        "sampler": [
+          {
+            "FirstRowsExecutionDataSampler": {
+              "sampleSize": "100"
+            }
+          },
+          {
+            "LastRowsExecutionDataSampler": {
+              "sampleSize": "100"
+            }
+          }
+        ]
+      }
+      """;
+
+  public boolean standardRunConfigurationsMissing(String templateHome) throws 
HopSetupException {
+    return !templateContains(templateHome, PIPELINE_RELATIVE)
+        || !templateContains(templateHome, WORKFLOW_RELATIVE);
+  }
+
+  public void ensure(String projectHome, String templateHome, 
HopEnvironmentApplyResult result)
+      throws HopSetupException {
+    boolean synthesized =
+        ensureRunConfiguration(
+            projectHome,
+            templateHome,
+            PIPELINE_RELATIVE,
+            PIPELINE_JSON,
+            "local pipeline run configuration",
+            result);
+    synthesized =
+        ensureRunConfiguration(
+                projectHome,
+                templateHome,
+                WORKFLOW_RELATIVE,
+                WORKFLOW_JSON,
+                "local workflow run configuration",
+                result)
+            || synthesized;
+    if (synthesized) {
+      ensureFile(
+          projectHome,
+          templateHome,
+          EXECUTION_INFO_RELATIVE,
+          EXECUTION_INFO_JSON,
+          "local execution info location",
+          result);
+      ensureFile(
+          projectHome,
+          templateHome,
+          DATA_PROFILE_RELATIVE,
+          DATA_PROFILE_JSON,
+          "execution data profile",
+          result);
+    } else {
+      copyFromTemplateIfMissing(
+          projectHome,
+          templateHome,
+          EXECUTION_INFO_RELATIVE,
+          "local execution info location",
+          result);
+      copyFromTemplateIfMissing(
+          projectHome, templateHome, DATA_PROFILE_RELATIVE, "execution data 
profile", result);
+    }
+  }
+
+  private boolean ensureRunConfiguration(
+      String projectHome,
+      String templateHome,
+      String relative,
+      String fallback,
+      String label,
+      HopEnvironmentApplyResult result)
+      throws HopSetupException {
+    String destination = projectHome + "/" + relative;
+    if (HopVfsFiles.exists(destination)) {
+      return false;
+    }
+    if (templateContains(templateHome, relative)) {
+      write(destination, HopVfsFiles.readUtf8(templateHome + "/" + relative), 
label, result);
+      return false;
+    }
+    write(destination, fallback, label, result);
+    return true;
+  }
+
+  private void ensureFile(
+      String projectHome,
+      String templateHome,
+      String relative,
+      String fallback,
+      String label,
+      HopEnvironmentApplyResult result)
+      throws HopSetupException {
+    String destination = projectHome + "/" + relative;
+    if (HopVfsFiles.exists(destination)) {
+      return;
+    }
+    String content =
+        templateContains(templateHome, relative)
+            ? HopVfsFiles.readUtf8(templateHome + "/" + relative)
+            : fallback;
+    write(destination, content, label, result);
+  }
+
+  private void copyFromTemplateIfMissing(
+      String projectHome,
+      String templateHome,
+      String relative,
+      String label,
+      HopEnvironmentApplyResult result)
+      throws HopSetupException {
+    String destination = projectHome + "/" + relative;
+    if (HopVfsFiles.exists(destination) || !templateContains(templateHome, 
relative)) {
+      return;
+    }
+    write(destination, HopVfsFiles.readUtf8(templateHome + "/" + relative), 
label, result);
+  }
+
+  private static boolean templateContains(String templateHome, String relative)
+      throws HopSetupException {
+    return StringUtils.isNotBlank(templateHome)
+        && HopVfsFiles.exists(templateHome + "/" + relative);
+  }
+
+  private static void write(
+      String destination, String content, String label, 
HopEnvironmentApplyResult result)
+      throws HopSetupException {
+    HopVfsFiles.writeUtf8(destination, content);
+    result.addMessage("Created " + label + " at " + destination);
+  }
+}
diff --git 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ProjectRegistrationWriter.java
 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ProjectRegistrationWriter.java
index edb7d6bf6b..bfedb21a38 100644
--- 
a/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ProjectRegistrationWriter.java
+++ 
b/plugins/misc/setup/src/main/java/org/apache/hop/setup/persist/ProjectRegistrationWriter.java
@@ -35,8 +35,10 @@ import org.apache.hop.setup.UserPaths;
 
 /**
  * Creates a user-owned default project and registers it (plus the install 
samples project) in the
- * target {@code hop-config.json}. Does not use the live {@code HopConfig} 
singleton, which still
- * points at the current process config folder.
+ * target {@code hop-config.json}. A new default project gets a {@code local} 
pipeline run
+ * configuration and a {@code local} workflow run configuration when it does 
not already have them.
+ * Does not use the live {@code HopConfig} singleton, which still points at 
the current process
+ * config folder.
  */
 public class ProjectRegistrationWriter {
 
@@ -55,10 +57,17 @@ public class ProjectRegistrationWriter {
       """;
 
   private final ObjectMapper mapper;
+  private final LocalRunConfigurationSeeder runConfigurations;
 
   public ProjectRegistrationWriter() {
+    this(new LocalRunConfigurationSeeder());
+  }
+
+  ProjectRegistrationWriter(LocalRunConfigurationSeeder runConfigurations) {
     mapper = HopJson.newMapper();
     mapper.enable(SerializationFeature.INDENT_OUTPUT);
+    this.runConfigurations =
+        runConfigurations == null ? new LocalRunConfigurationSeeder() : 
runConfigurations;
   }
 
   public void apply(
@@ -118,6 +127,10 @@ public class ProjectRegistrationWriter {
           hasTemplate
               ? "Would copy default project template to " + defaultHome
               : "Would create default project at " + defaultHome);
+      if (runConfigurations.standardRunConfigurationsMissing(hasTemplate ? 
template : null)) {
+        result.addMessage(
+            "Would add local pipeline and workflow run configurations to " + 
defaultHome);
+      }
       return;
     }
     HopVfsFiles.createFolder(defaultHome);
@@ -128,6 +141,7 @@ public class ProjectRegistrationWriter {
       HopVfsFiles.writeUtf8(projectConfig, MINIMAL_PROJECT_CONFIG);
       result.addMessage("Created default project at " + defaultHome);
     }
+    runConfigurations.ensure(defaultHome, hasTemplate ? template : null, 
result);
   }
 
   private String resolveSamplesHome(Path hopHome, HopEnvironmentApplyResult 
result)
diff --git 
a/plugins/misc/setup/src/main/resources/org/apache/hop/setup/gui/messages/messages_en_US.properties
 
b/plugins/misc/setup/src/main/resources/org/apache/hop/setup/gui/messages/messages_en_US.properties
index 1d5bda47f2..cd80d9ce51 100644
--- 
a/plugins/misc/setup/src/main/resources/org/apache/hop/setup/gui/messages/messages_en_US.properties
+++ 
b/plugins/misc/setup/src/main/resources/org/apache/hop/setup/gui/messages/messages_en_US.properties
@@ -26,7 +26,7 @@ SetupDialog.Web.ReadOnly=Hop Web cannot write the server 
operating system enviro
 SetupDialog.Tab.Variables=Variables
 SetupDialog.Tab.Location=Location
 SetupDialog.Tab.Projects=Projects
-SetupDialog.Projects.Intro=After you point Hop at a new config folder it 
starts without projects. Create a default project in your documents folder and 
register the samples project from this Hop installation.
+SetupDialog.Projects.Intro=After you point Hop at a new config folder it 
starts without projects. Create a default project in your documents folder, 
including a local pipeline and workflow run configuration, and register the 
samples project from this Hop installation.
 SetupDialog.Projects.CreateDefault=Create a default project
 SetupDialog.Projects.DefaultHome.Label=Default project home
 SetupDialog.Projects.Recommended=Use recommended project folder
diff --git 
a/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ConfigFolderSeederTest.java
 
b/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ConfigFolderSeederTest.java
index 53a92bf374..496cb2b147 100644
--- 
a/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ConfigFolderSeederTest.java
+++ 
b/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ConfigFolderSeederTest.java
@@ -52,6 +52,17 @@ class ConfigFolderSeederTest {
     Files.createDirectories(install.resolve("config"));
     
Files.writeString(install.resolve("config").resolve(HopSetupVariables.HOP_CONFIG_JSON),
 "{}");
     Files.writeString(install.resolve("config").resolve("extra.txt"), "keep");
+    Path defaultProject =
+        install
+            
.resolve("config/projects/default/metadata/pipeline-run-configuration")
+            .resolve("local.json");
+    Files.createDirectories(defaultProject.getParent());
+    Files.writeString(defaultProject, "{ \"name\": \"local\" }");
+    Path samplesProject = install.resolve("config/projects/samples");
+    Files.createDirectories(samplesProject);
+    Files.writeString(
+        samplesProject.resolve(HopSetupVariables.PROJECT_CONFIG_FILENAME),
+        "{ \"description\": \"samples\" }");
     Path target = temp.resolve("user-config");
     HopEnvironmentSpec spec = new HopEnvironmentSpec();
     spec.setConfigFolder(target.toString());
@@ -60,6 +71,9 @@ class ConfigFolderSeederTest {
     new ConfigFolderSeeder().seed(spec, new HopEnvironmentApplyResult(), 
install);
     
assertTrue(Files.exists(target.resolve(HopSetupVariables.HOP_CONFIG_JSON)));
     assertTrue(Files.exists(target.resolve("extra.txt")));
+    assertTrue(Files.isDirectory(target.resolve("projects")));
+    assertFalse(Files.exists(target.resolve("projects/default")));
+    assertFalse(Files.exists(target.resolve("projects/samples")));
   }
 
   @Test
diff --git 
a/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/LocalRunConfigurationSeederTest.java
 
b/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/LocalRunConfigurationSeederTest.java
new file mode 100644
index 0000000000..a6e731dece
--- /dev/null
+++ 
b/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/LocalRunConfigurationSeederTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.hop.setup.persist;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.hop.setup.HopEnvironmentApplyResult;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class LocalRunConfigurationSeederTest {
+
+  @TempDir Path temp;
+
+  @Test
+  void writesStandardLocalRunConfigurationsWhenTemplateHasNone() throws 
Exception {
+    Path project = temp.resolve("default");
+    Files.createDirectories(project);
+    HopEnvironmentApplyResult result = new HopEnvironmentApplyResult();
+
+    new LocalRunConfigurationSeeder().ensure(project.toString(), null, result);
+
+    String pipeline =
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE));
+    String workflow =
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.WORKFLOW_RELATIVE));
+    assertTrue(pipeline.contains("\"name\": \"local\""));
+    assertTrue(pipeline.contains("\"defaultSelection\": true"));
+    assertTrue(pipeline.contains("\"executionInfoLocationName\": 
\"local-audit\""));
+    assertTrue(pipeline.contains("\"dataProfile\": \"first-last\""));
+    assertTrue(workflow.contains("\"name\": \"local\""));
+    assertTrue(workflow.contains("\"defaultSelection\": true"));
+    assertTrue(
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.EXECUTION_INFO_RELATIVE))
+            .contains("${HOP_AUDIT_FOLDER}/executions/"));
+    assertTrue(
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.DATA_PROFILE_RELATIVE))
+            .contains("first-last"));
+    assertTrue(result.describe().contains("local pipeline run configuration"));
+    assertTrue(result.describe().contains("local workflow run configuration"));
+  }
+
+  @Test
+  void keepsTemplateRunConfigurationsAndDoesNotInventCompanions() throws 
Exception {
+    Path template = temp.resolve("template");
+    write(
+        template, LocalRunConfigurationSeeder.PIPELINE_RELATIVE, "{ \"name\": 
\"kept-pipeline\" }");
+    write(
+        template, LocalRunConfigurationSeeder.WORKFLOW_RELATIVE, "{ \"name\": 
\"kept-workflow\" }");
+    Path project = temp.resolve("default");
+    Files.createDirectories(project);
+
+    new LocalRunConfigurationSeeder()
+        .ensure(project.toString(), template.toString(), new 
HopEnvironmentApplyResult());
+
+    assertTrue(
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE))
+            .contains("kept-pipeline"));
+    assertTrue(
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.WORKFLOW_RELATIVE))
+            .contains("kept-workflow"));
+    
assertFalse(Files.exists(project.resolve(LocalRunConfigurationSeeder.EXECUTION_INFO_RELATIVE)));
+    
assertFalse(Files.exists(project.resolve(LocalRunConfigurationSeeder.DATA_PROFILE_RELATIVE)));
+  }
+
+  @Test
+  void doesNotReplaceExistingRunConfigurations() throws Exception {
+    Path project = temp.resolve("default");
+    write(project, LocalRunConfigurationSeeder.PIPELINE_RELATIVE, "{ \"name\": 
\"mine\" }");
+    write(project, LocalRunConfigurationSeeder.WORKFLOW_RELATIVE, "{ \"name\": 
\"mine-wf\" }");
+
+    new LocalRunConfigurationSeeder()
+        .ensure(project.toString(), null, new HopEnvironmentApplyResult());
+
+    assertTrue(
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE))
+            .contains("mine"));
+    assertTrue(
+        
Files.readString(project.resolve(LocalRunConfigurationSeeder.WORKFLOW_RELATIVE))
+            .contains("mine-wf"));
+    
assertFalse(Files.exists(project.resolve(LocalRunConfigurationSeeder.EXECUTION_INFO_RELATIVE)));
+  }
+
+  private static void write(Path root, String relative, String content) throws 
Exception {
+    Path file = root.resolve(relative);
+    Files.createDirectories(file.getParent());
+    Files.writeString(file, content);
+  }
+}
diff --git 
a/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ProjectRegistrationWriterTest.java
 
b/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ProjectRegistrationWriterTest.java
index d80345b54b..b440502945 100644
--- 
a/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ProjectRegistrationWriterTest.java
+++ 
b/plugins/misc/setup/src/test/java/org/apache/hop/setup/persist/ProjectRegistrationWriterTest.java
@@ -79,6 +79,12 @@ class ProjectRegistrationWriterTest {
             hopHome);
 
     
assertTrue(Files.exists(defaultHome.resolve(HopSetupVariables.PROJECT_CONFIG_FILENAME)));
+    assertTrue(
+        
Files.readString(defaultHome.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE))
+            .contains("\"name\": \"local\""));
+    assertTrue(
+        
Files.readString(defaultHome.resolve(LocalRunConfigurationSeeder.WORKFLOW_RELATIVE))
+            .contains("\"name\": \"local\""));
     String hopConfig = 
Files.readString(configFolder.resolve(HopSetupVariables.HOP_CONFIG_JSON));
     assertTrue(hopConfig.contains("\"projectName\" : \"default\""));
     assertTrue(hopConfig.contains(defaultHome.toString()));
@@ -87,6 +93,78 @@ class ProjectRegistrationWriterTest {
     assertTrue(hopConfig.contains("\"defaultProject\" : \"default\""));
   }
 
+  @Test
+  void copiesNestedTemplateRunConfigurations() throws Exception {
+    Path hopHome = temp.resolve("hop");
+    Path template = hopHome.resolve("config/projects/default");
+    
Files.createDirectories(template.resolve("metadata/pipeline-run-configuration"));
+    
Files.createDirectories(template.resolve("metadata/workflow-run-configuration"));
+    Files.writeString(
+        template.resolve(HopSetupVariables.PROJECT_CONFIG_FILENAME),
+        "{ \"description\": \"template\" }");
+    Files.writeString(
+        template.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE),
+        "{ \"name\": \"from-template\" }");
+    Files.writeString(
+        template.resolve(LocalRunConfigurationSeeder.WORKFLOW_RELATIVE),
+        "{ \"name\": \"from-template-wf\" }");
+
+    Path defaultHome = temp.resolve("Documents/hop/default");
+    HopEnvironmentSpec spec = new HopEnvironmentSpec();
+    spec.setConfigFolder(temp.resolve("user-config").toString());
+    spec.setCreateDefaultProject(true);
+    spec.setDefaultProjectHome(defaultHome.toString());
+
+    new ProjectRegistrationWriter()
+        .apply(
+            spec,
+            OsFamily.UNIX,
+            unixPaths(temp.resolve("home")),
+            new HopEnvironmentApplyResult(),
+            hopHome);
+
+    assertTrue(
+        
Files.readString(defaultHome.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE))
+            .contains("from-template"));
+    assertTrue(
+        
Files.readString(defaultHome.resolve(LocalRunConfigurationSeeder.WORKFLOW_RELATIVE))
+            .contains("from-template-wf"));
+    assertFalse(
+        
Files.exists(defaultHome.resolve(LocalRunConfigurationSeeder.EXECUTION_INFO_RELATIVE)));
+  }
+
+  @Test
+  void copiesNestedTemplateSubfolders() throws Exception {
+    Path hopHome = temp.resolve("hop");
+    Path template = hopHome.resolve("config/projects/default");
+    Files.createDirectories(template.resolve("metadata/rdbms"));
+    Files.createDirectories(template.resolve("datasets"));
+    Files.writeString(
+        template.resolve(HopSetupVariables.PROJECT_CONFIG_FILENAME),
+        "{ \"description\": \"template\" }");
+    Files.writeString(template.resolve("metadata/rdbms/sample.json"), "{ 
\"name\": \"sample\" }");
+    Files.writeString(template.resolve("datasets/customers.csv"), "id,name");
+
+    Path defaultHome = temp.resolve("Documents/hop/default");
+    HopEnvironmentSpec spec = new HopEnvironmentSpec();
+    spec.setConfigFolder(temp.resolve("user-config").toString());
+    spec.setCreateDefaultProject(true);
+    spec.setDefaultProjectHome(defaultHome.toString());
+
+    new ProjectRegistrationWriter()
+        .apply(
+            spec,
+            OsFamily.UNIX,
+            unixPaths(temp.resolve("home")),
+            new HopEnvironmentApplyResult(),
+            hopHome);
+
+    assertTrue(
+        
Files.readString(defaultHome.resolve("metadata/rdbms/sample.json")).contains("sample"));
+    
assertTrue(Files.readString(defaultHome.resolve("datasets/customers.csv")).contains("id,name"));
+    
assertTrue(Files.exists(defaultHome.resolve(HopSetupVariables.PROJECT_CONFIG_FILENAME)));
+  }
+
   @Test
   void doesNotOverwriteExistingDefaultProjectConfig() throws Exception {
     Path defaultHome = temp.resolve("docs/hop/default");
@@ -112,6 +190,7 @@ class ProjectRegistrationWriterTest {
     assertTrue(
         
Files.readString(defaultHome.resolve(HopSetupVariables.PROJECT_CONFIG_FILENAME))
             .contains("keep"));
+    
assertFalse(Files.exists(defaultHome.resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE)));
   }
 
   @Test
@@ -130,7 +209,10 @@ class ProjectRegistrationWriterTest {
         .apply(spec, OsFamily.UNIX, unixPaths(temp.resolve("home")), result, 
null);
 
     
assertFalse(Files.exists(configFolder.resolve(HopSetupVariables.HOP_CONFIG_JSON)));
-    assertTrue(result.describe().contains("Would"));
+    assertTrue(
+        result.describe().contains("Would add local pipeline and workflow run 
configurations"));
+    assertFalse(
+        
Files.exists(temp.resolve("p").resolve(LocalRunConfigurationSeeder.PIPELINE_RELATIVE)));
   }
 
   @Test

Reply via email to