Repository: microsoft/kiota (csharp)

Issue:
Support `Add plugin` flow coming from TTK
To support `Add plugin` flow coming from TTK, we need to provide an additional parameter so they can specify the folder we should be generating the files in:

```jsonc
kiota.openApiExplorer.searchOrOpenApiDescription:
{
      kind: "Plugin",
      type: "ApiPlugin",
      source: "ttk",
      ttkContext,
      projectPath: "c:/app"
}
```

If the `projectPath` is not provided, Kiota should use the temp folder.

After creating the files, if the source is ttk, Kiota needs to call TTK back.

## Acceptance Criteria

1. One should be able to provide a `projectPath` parameter to create the clients/plugins to an specific folder.
2. If `projectPath` is not provided, Kiota should create default to the temp folder.
3. If source is provided, Kiota should call/complete the generation as expected based on the source.
4. Relative paths should not be allowed. If a relative path is provided, Kiota should thrown an error "Relative path is not supported for projectPath parameter".

Pull request:
### Overview 
Closes #5751

### Notes
- if the provided path does not exist, it is created. If that doesn't work, the temporary folder is used.

Patch:
diff --git a/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts b/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts
index 99b896ede6..d570a8bee8 100644
--- a/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts
+++ b/vscode/microsoft-kiota/src/commands/generate/generateClientCommand.ts
@@ -62,7 +62,7 @@ export class GenerateClientCommand extends Command {
       if (!deepLinkParams.name && this._openApiTreeProvider.apiTitle) {
         deepLinkParams.name = getSanitizedString(this._openApiTreeProvider.apiTitle);
       }
-      availableStateInfo = transformToGenerationConfig(deepLinkParams);
+      availableStateInfo = await transformToGenerationConfig(deepLinkParams);
     } else {
       const pluginName = getSanitizedString(this._openApiTreeProvider.apiTitle);
       availableStateInfo = {
diff --git a/vscode/microsoft-kiota/src/utilities/deep-linking.ts b/vscode/microsoft-kiota/src/utilities/deep-linking.ts
index c7b5dbfc30..d9d2c2567f 100644
--- a/vscode/microsoft-kiota/src/utilities/deep-linking.ts
+++ b/vscode/microsoft-kiota/src/utilities/deep-linking.ts
@@ -1,3 +1,6 @@
+import * as path from 'path';
+import { promises as fs } from 'fs';
+
 import { GenerateState } from "../modules/steps/generateSteps";
 import { KiotaGenerationLanguage, KiotaPluginType } from "../types/enums";
 import { allGenerationLanguagesToString, getSanitizedString, parseGenerationLanguage, parsePluginType } from "../util";
@@ -8,8 +11,8 @@ export function isDeeplinkEnabled(deepLinkParams: Partial<IntegrationParams>): b
   return Object.values(deepLinkParams).filter(property => property).length >= minimumNumberOfParams;
 }
 
-export function transformToGenerationConfig(deepLinkParams: Partial<IntegrationParams>)
-  : Partial<GenerateState> {
+export async function transformToGenerationConfig(deepLinkParams: Partial<IntegrationParams>)
+  : Promise<Partial<GenerateState>> {
   const generationConfig: Partial<GenerateState> = {};
   if (deepLinkParams.kind === "client") {
     generationConfig.generationType = "client";
@@ -34,7 +37,7 @@ export function transformToGenerationConfig(deepLinkParams: Partial<IntegrationP
     }
     generationConfig.outputPath =
       (deepLinkParams.source && deepLinkParams.source?.toLowerCase() === 'ttk')
-        ? createTemporaryFolder()
+      ? await determineOutputPath(deepLinkParams)
         : undefined;
   }
   return generationConfig;
@@ -49,7 +52,8 @@ export interface IntegrationParams {
   source: string;
   ttkContext: {
     lastCommand: string;
-  }
+  },
+  projectPath: string;
 };
 
 export function validateDeepLinkQueryParams(queryParameters: Partial<IntegrationParams>):
@@ -59,6 +63,8 @@ export function validateDeepLinkQueryParams(queryParameters: Partial<Integration
   const descriptionurl = queryParameters["descriptionurl"];
   const name = getSanitizedString(queryParameters["name"]);
   const source = getSanitizedString(queryParameters["source"]);
+  let projectPath = queryParameters["projectPath"];
+
   let lowercasedKind: string = queryParameters["kind"]?.toLowerCase() ?? "";
   let validKind: string | undefined = ["plugin", "client"].indexOf(lowercasedKind) > -1 ? lowercasedKind : undefined;
   if (!validKind) {
@@ -103,14 +109,38 @@ export function validateDeepLinkQueryParams(queryParameters: Partial<Integration
     errormsg.push("Invalid parameter 'type' deeplinked. Expected values: " + acceptedPluginTypes.join(","));
   }
 
+  if (projectPath && !path.isAbsolute(projectPath)) {
+    projectPath = undefined;
+    errormsg.push(`A relative paths is not supported for the projectPath parameter`);
+  }
   validQueryParams = {
-    descriptionurl: descriptionurl,
-    name: name,
+    descriptionurl,
+    name,
     kind: validKind,
     type: providedType,
     language: givenLanguage,
-    source: source,
-    ttkContext: queryParameters.ttkContext ? queryParameters.ttkContext : undefined
+    source,
+    ttkContext: queryParameters.ttkContext,
+    projectPath
   };
   return [validQueryParams, errormsg];
 }
+
+async function determineOutputPath(deepLinkParams: Partial<IntegrationParams>): Promise<string | undefined> {
+  if (deepLinkParams.projectPath) {
+    try {
+      const exists = await fs.access(deepLinkParams.projectPath).then(() => true).catch(() => false);
+      if (!exists) {
+        try {
+          await fs.mkdir(deepLinkParams.projectPath);
+        } catch (err: unknown) {
+          throw new Error(`Error creating directory: ${(err as Error).message}`);
+        }
+      }
+      return deepLinkParams.projectPath;
+    } catch (error) {
+      return createTemporaryFolder();
+    }
+  }
+  return createTemporaryFolder();
+}


Test patch:
diff --git a/vscode/microsoft-kiota/src/test/suite/commands/generateClientCommand.test.ts b/vscode/microsoft-kiota/src/test/suite/commands/generateClientCommand.test.ts
index edc0337b60..448448c9d3 100644
--- a/vscode/microsoft-kiota/src/test/suite/commands/generateClientCommand.test.ts
+++ b/vscode/microsoft-kiota/src/test/suite/commands/generateClientCommand.test.ts
@@ -173,7 +173,7 @@ suite('GenerateClientCommand Test Suite', () => {
         assert.strictEqual(!treeProvider.descriptionUrl, false);
         vscodeWindowSpy.verify();
         sinon.assert.calledOnceWithMatch(getlanguageInfoFn, context);
-        let stateInfo = transformToGenerationConfig(pluginParams);
+        let stateInfo = await transformToGenerationConfig(pluginParams);
         sinon.assert.calledOnceWithMatch(generateStepsFn, stateInfo, undefined , pluginParams);
         sinon.assert.calledOnce(showUpgradeWarningMessageStub);
         sinon.assert.calledOnceWithMatch(getExtensionSettingsStub, "kiota");