Repository: microsoft/kiota (csharp)

Issue:
kiota config adjustements
Make the following adjustments to the kiota config implementation:
- [x] create a subfolder per client for description copies in case the description brings multiple files
- [x] rename config commands to workspace
- [x] move both manifest and config to ./kiota
- [x] rename config file to workspace.json

Pull request description:
fixes #4310

Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index e04fc3d4b1..06fa4a05f2 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -271,7 +271,7 @@
       "request": "launch",
       "preLaunchTask": "build",
       "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll",
-      "args": ["config", "migrate"],
+      "args": ["workspace", "migrate"],
       "cwd": "${workspaceFolder}/samples/msgraph-mail/dotnet",
       "console": "internalConsole",
       "stopAtEntry": false,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d649bdd4a..e783ce0b56 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+- PREVIEW: Renamed the config commands to workspace. [#4310](https://github.com/microsoft/kiota/issues/4310)
+- PREVIEW: Moved preview configuration files to the .kiota directory. [#4310](https://github.com/microsoft/kiota/issues/4310)
+- PREVIEW: Moved the copy descriptions to dedicated folders. [#4310](https://github.com/microsoft/kiota/issues/4310)
+- PREVIEW: Renamed the config to workspace file. [#4310](https://github.com/microsoft/kiota/issues/4310)
+
 ## [1.12.0] - 2024-03-06
 
 ### Added
diff --git a/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs b/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs
index 9a5e6006ce..170f1f2bd0 100644
--- a/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs
+++ b/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs
@@ -21,12 +21,13 @@ public DescriptionStorageService(string targetDirectory)
         o.PoolSize = 20;
         o.PoolInitialFill = 1;
     });
+    private string GetDescriptionFilePath(string clientName, string extension) => Path.Combine(TargetDirectory, DescriptionsSubDirectoryRelativePath, clientName, $"description.{extension}");
     public async Task UpdateDescriptionAsync(string clientName, Stream description, string extension = "yml", CancellationToken cancellationToken = default)
     {
         ArgumentNullException.ThrowIfNull(clientName);
         ArgumentNullException.ThrowIfNull(description);
         ArgumentNullException.ThrowIfNull(extension);
-        var descriptionFilePath = Path.Combine(TargetDirectory, DescriptionsSubDirectoryRelativePath, $"{clientName}.{extension}");
+        var descriptionFilePath = GetDescriptionFilePath(clientName, extension);
         using (await localFilesLock.LockAsync(descriptionFilePath, cancellationToken).ConfigureAwait(false))
         {
             Directory.CreateDirectory(Path.GetDirectoryName(descriptionFilePath) ?? throw new InvalidOperationException("The target path is invalid"));
@@ -39,7 +40,7 @@ public async Task UpdateDescriptionAsync(string clientName, Stream description,
     {
         ArgumentNullException.ThrowIfNull(clientName);
         ArgumentNullException.ThrowIfNull(extension);
-        var descriptionFilePath = Path.Combine(TargetDirectory, DescriptionsSubDirectoryRelativePath, $"{clientName}.{extension}");
+        var descriptionFilePath = GetDescriptionFilePath(clientName, extension);
         if (!File.Exists(descriptionFilePath))
             return null;
         using (await localFilesLock.LockAsync(descriptionFilePath, cancellationToken).ConfigureAwait(false))
@@ -55,7 +56,7 @@ public void RemoveDescription(string clientName, string extension = "yml")
     {
         ArgumentNullException.ThrowIfNull(clientName);
         ArgumentNullException.ThrowIfNull(extension);
-        var descriptionFilePath = Path.Combine(TargetDirectory, DescriptionsSubDirectoryRelativePath, $"{clientName}.{extension}");
+        var descriptionFilePath = GetDescriptionFilePath(clientName, extension);
         if (File.Exists(descriptionFilePath))
             File.Delete(descriptionFilePath);
     }
diff --git a/src/Kiota.Builder/WorkspaceManagement/WorkspaceConfigurationStorageService.cs b/src/Kiota.Builder/WorkspaceManagement/WorkspaceConfigurationStorageService.cs
index 02cb20ca51..4a6c1e805f 100644
--- a/src/Kiota.Builder/WorkspaceManagement/WorkspaceConfigurationStorageService.cs
+++ b/src/Kiota.Builder/WorkspaceManagement/WorkspaceConfigurationStorageService.cs
@@ -13,8 +13,9 @@ namespace Kiota.Builder.WorkspaceManagement;
 
 public class WorkspaceConfigurationStorageService
 {
-    public const string ConfigurationFileName = "kiota-config.json";
+    public const string ConfigurationFileName = "workspace.json";
     public const string ManifestFileName = "apimanifest.json";
+    public static string KiotaDirectorySegment => DescriptionStorageService.KiotaDirectorySegment;
     public string TargetDirectory
     {
         get; private set;
@@ -25,7 +26,7 @@ public string TargetDirectory
     public WorkspaceConfigurationStorageService(string targetDirectory)
     {
         ArgumentException.ThrowIfNullOrEmpty(targetDirectory);
-        TargetDirectory = targetDirectory;
+        TargetDirectory = Path.Combine(targetDirectory, KiotaDirectorySegment);
         targetConfigurationFilePath = Path.Combine(TargetDirectory, ConfigurationFileName);
         targetManifestFilePath = Path.Combine(TargetDirectory, ManifestFileName);
     }
diff --git a/src/kiota/Handlers/Client/AddHandler.cs b/src/kiota/Handlers/Client/AddHandler.cs
index 16cf680451..86b1d706da 100644
--- a/src/kiota/Handlers/Client/AddHandler.cs
+++ b/src/kiota/Handlers/Client/AddHandler.cs
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.CommandLine;
 using System.CommandLine.Invocation;
+using System.IO;
 using System.Linq;
 using System.Text.Json;
 using System.Threading;
@@ -131,7 +132,7 @@ public override async Task<int> InvokeAsync(InvocationContext context)
                     DisplaySuccess("Generation skipped as --skip-generation was passed");
                     DisplayGenerateCommandHint();
                 } // else we get an error because we're adding a client that already exists
-                var manifestPath = $"{GetAbsolutePath(WorkspaceConfigurationStorageService.ManifestFileName)}#{Configuration.Generation.ClientClassName}";
+                var manifestPath = $"{GetAbsolutePath(Path.Combine(WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ManifestFileName))}#{Configuration.Generation.ClientClassName}";
                 DisplayInfoHint(language, string.Empty, manifestPath);
                 DisplayGenerateAdvancedHint(includePatterns, excludePatterns, string.Empty, manifestPath, "client add");
                 return 0;
diff --git a/src/kiota/Handlers/Client/EditHandler.cs b/src/kiota/Handlers/Client/EditHandler.cs
index c989d270d0..9a0a764a64 100644
--- a/src/kiota/Handlers/Client/EditHandler.cs
+++ b/src/kiota/Handlers/Client/EditHandler.cs
@@ -153,7 +153,7 @@ public override async Task<int> InvokeAsync(InvocationContext context)
                     DisplayWarning("Generation skipped as no changes were detected");
                     DisplayCleanHint("client generate", "--refresh");
                 }
-                var manifestPath = $"{GetAbsolutePath(WorkspaceConfigurationStorageService.ManifestFileName)}#{Configuration.Generation.ClientClassName}";
+                var manifestPath = $"{GetAbsolutePath(Path.Combine(WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ManifestFileName))}#{Configuration.Generation.ClientClassName}";
                 DisplayInfoHint(Configuration.Generation.Language, string.Empty, manifestPath);
                 DisplayGenerateAdvancedHint(includePatterns ?? [], excludePatterns ?? [], string.Empty, manifestPath, "client edit");
                 return 0;
diff --git a/src/kiota/Handlers/Client/GenerateHandler.cs b/src/kiota/Handlers/Client/GenerateHandler.cs
index bde2dbb499..51c011125b 100644
--- a/src/kiota/Handlers/Client/GenerateHandler.cs
+++ b/src/kiota/Handlers/Client/GenerateHandler.cs
@@ -67,7 +67,7 @@ public override async Task<int> InvokeAsync(InvocationContext context)
                     if (result)
                     {
                         DisplaySuccess($"Update of {clientEntry.Key} client completed");
-                        var manifestPath = $"{GetAbsolutePath(WorkspaceConfigurationStorageService.ManifestFileName)}#{clientEntry.Key}";
+                        var manifestPath = $"{GetAbsolutePath(Path.Combine(WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ManifestFileName))}#{clientEntry.Key}";
                         DisplayInfoHint(generationConfiguration.Language, string.Empty, manifestPath);
                     }
                     else
diff --git a/src/kiota/Handlers/Config/InitHandler.cs b/src/kiota/Handlers/Workspace/InitHandler.cs
similarity index 97%
rename from src/kiota/Handlers/Config/InitHandler.cs
rename to src/kiota/Handlers/Workspace/InitHandler.cs
index 359411627a..90fbbc9116 100644
--- a/src/kiota/Handlers/Config/InitHandler.cs
+++ b/src/kiota/Handlers/Workspace/InitHandler.cs
@@ -6,7 +6,7 @@
 using Kiota.Builder.WorkspaceManagement;
 using Microsoft.Extensions.Logging;
 
-namespace kiota.Handlers.Config;
+namespace kiota.Handlers.Workspace;
 
 internal class InitHandler : BaseKiotaCommandHandler
 {
diff --git a/src/kiota/Handlers/Config/MigrateHandler.cs b/src/kiota/Handlers/Workspace/MigrateHandler.cs
similarity index 98%
rename from src/kiota/Handlers/Config/MigrateHandler.cs
rename to src/kiota/Handlers/Workspace/MigrateHandler.cs
index cb84720b8b..abd7f956f3 100644
--- a/src/kiota/Handlers/Config/MigrateHandler.cs
+++ b/src/kiota/Handlers/Workspace/MigrateHandler.cs
@@ -8,7 +8,7 @@
 using Kiota.Builder.WorkspaceManagement;
 using Microsoft.Extensions.Logging;
 
-namespace kiota.Handlers.Config;
+namespace kiota.Handlers.Workspace;
 
 internal class MigrateHandler : BaseKiotaCommandHandler
 {
diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs
index ba985ba0dd..91317c3bba 100644
--- a/src/kiota/KiotaHost.cs
+++ b/src/kiota/KiotaHost.cs
@@ -31,7 +31,7 @@ public static RootCommand GetRootCommand()
         rootCommand.AddCommand(GetRpcCommand());
         if (IsConfigPreviewEnabled.Value)
         {
-            rootCommand.AddCommand(KiotaConfigCommands.GetConfigNodeCommand());
+            rootCommand.AddCommand(KiotaWorkspaceCommands.GetWorkspaceNodeCommand());
             rootCommand.AddCommand(KiotaClientCommands.GetClientNodeCommand());
         }
         return rootCommand;
diff --git a/src/kiota/KiotaConfigCommands.cs b/src/kiota/KiotaWorkspaceCommands.cs
similarity index 82%
rename from src/kiota/KiotaConfigCommands.cs
rename to src/kiota/KiotaWorkspaceCommands.cs
index 59e0e8f63d..5d56db97c7 100644
--- a/src/kiota/KiotaConfigCommands.cs
+++ b/src/kiota/KiotaWorkspaceCommands.cs
@@ -1,13 +1,13 @@
 ﻿using System.CommandLine;
-using kiota.Handlers.Config;
+using kiota.Handlers.Workspace;
 
 namespace kiota;
 
-public static class KiotaConfigCommands
+public static class KiotaWorkspaceCommands
 {
-    public static Command GetConfigNodeCommand()
+    public static Command GetWorkspaceNodeCommand()
     {
-        var command = new Command("config", "Manages the Kiota configuration");
+        var command = new Command("workspace", "Manages the Kiota workspace configuration");
         command.AddCommand(GetInitCommand());
         command.AddCommand(GetMigrateCommand());
         return command;
@@ -15,7 +15,7 @@ public static Command GetConfigNodeCommand()
     private static Command GetInitCommand()
     {
         var logLevelOption = KiotaHost.GetLogLevelOption();
-        var command = new Command("init", "Initializes the Kiota configuration"){
+        var command = new Command("init", "Initializes the Kiota workspace configuration"){
             logLevelOption,
         };
         command.Handler = new InitHandler
@@ -29,7 +29,7 @@ private static Command GetMigrateCommand()
         var logLevelOption = KiotaHost.GetLogLevelOption();
         var lockDirectoryOption = GetLockDirectoryOption();
         var classOption = KiotaClientCommands.GetClientNameOption(false);
-        var command = new Command("migrate", "Migrates a kiota lock file to a Kiota configuration")
+        var command = new Command("migrate", "Migrates a kiota lock file to a Kiota workspace configuration")
         {
             logLevelOption,
             lockDirectoryOption,


Test patch:
diff --git a/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceConfigurationStorageServiceTests.cs b/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceConfigurationStorageServiceTests.cs
index d8002d3a65..4600b9acda 100644
--- a/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceConfigurationStorageServiceTests.cs
+++ b/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceConfigurationStorageServiceTests.cs
@@ -20,7 +20,7 @@ public async Task InitializesAsync()
     {
         var service = new WorkspaceConfigurationStorageService(tempPath);
         await service.InitializeAsync();
-        Assert.True(File.Exists(Path.Combine(tempPath, "kiota-config.json")));
+        Assert.True(File.Exists(Path.Combine(tempPath, WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ConfigurationFileName)));
     }
     [Fact]
     public async Task FailsOnDoubleInit()
@@ -67,7 +67,7 @@ public async Task BackupsAndRestores()
         var service = new WorkspaceConfigurationStorageService(tempPath);
         await service.InitializeAsync();
         await service.BackupConfigAsync();
-        var targetConfigFile = Path.Combine(tempPath, "kiota-config.json");
+        var targetConfigFile = Path.Combine(tempPath, WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ConfigurationFileName);
         File.Delete(targetConfigFile);
         Assert.False(File.Exists(targetConfigFile));
         await service.RestoreConfigAsync();
diff --git a/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceManagementServiceTests.cs b/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceManagementServiceTests.cs
index 925e5b0e65..f862bd24a7 100644
--- a/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceManagementServiceTests.cs
+++ b/tests/Kiota.Builder.Tests/WorkspaceManagement/WorkspaceManagementServiceTests.cs
@@ -172,9 +172,9 @@ await File.WriteAllTextAsync(descriptionPath, @$"openapi: 3.0.1
         Assert.Single(clientNames);
         Assert.Equal("clientName", clientNames.First());
         Assert.False(File.Exists(Path.Combine(tempPath, LockManagementService.LockFileName)));
-        Assert.True(File.Exists(Path.Combine(tempPath, WorkspaceConfigurationStorageService.ConfigurationFileName)));
-        Assert.True(File.Exists(Path.Combine(tempPath, WorkspaceConfigurationStorageService.ManifestFileName)));
-        Assert.True(File.Exists(Path.Combine(tempPath, DescriptionStorageService.DescriptionsSubDirectoryRelativePath, "clientName.yml")));
+        Assert.True(File.Exists(Path.Combine(tempPath, WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ConfigurationFileName)));
+        Assert.True(File.Exists(Path.Combine(tempPath, WorkspaceConfigurationStorageService.KiotaDirectorySegment, WorkspaceConfigurationStorageService.ManifestFileName)));
+        Assert.True(File.Exists(Path.Combine(tempPath, DescriptionStorageService.DescriptionsSubDirectoryRelativePath, "clientName", "description.yml")));
     }
     [InlineData(true, true)]
     [InlineData(true, false)]
@@ -186,7 +186,7 @@ public async Task GetsADescription(bool usesConfig, bool cleanOutput)
         var mockLogger = Mock.Of<ILogger>();
         Directory.CreateDirectory(tempPath);
         var service = new WorkspaceManagementService(mockLogger, httpClient, usesConfig, tempPath);
-        var descriptionPath = Path.Combine(tempPath, $"{DescriptionStorageService.DescriptionsSubDirectoryRelativePath}/clientName.yml");
+        var descriptionPath = Path.Combine(tempPath, $"{DescriptionStorageService.DescriptionsSubDirectoryRelativePath}/clientName/description.yml");
         var outputPath = Path.Combine(tempPath, "client");
         Directory.CreateDirectory(outputPath);
         Directory.CreateDirectory(Path.GetDirectoryName(descriptionPath));