Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, it, expect, vi } from 'vitest';
import { TargetFramework } from '@microsoft/vscode-extension-logic-apps';

vi.mock('fs-extra');
vi.mock('vscode');
vi.mock('../../../../../extensionVariables', () => ({
ext: { outputChannel: { appendLog: vi.fn() } },
}));
vi.mock('../../../../../localize', () => ({
localize: (_key: string, msg: string) => msg,
}));

import { FunctionFileStep } from '../functionFileStep';

describe('FunctionFileStep', () => {
describe('csTemplateFileName mapping', () => {
it('should map Net10 to FunctionsFileNet10', () => {
const step = new FunctionFileStep();
const mapping = (step as any).csTemplateFileName;
expect(mapping[TargetFramework.Net10]).toBe('FunctionsFileNet10');
});

it('should preserve Net8 mapping', () => {
const step = new FunctionFileStep();
const mapping = (step as any).csTemplateFileName;
expect(mapping[TargetFramework.Net8]).toBe('FunctionsFileNet8');
});

it('should preserve NetFx mapping', () => {
const step = new FunctionFileStep();
const mapping = (step as any).csTemplateFileName;
expect(mapping[TargetFramework.NetFx]).toBe('FunctionsFileNetFx');
});

it('should contain exactly three framework entries', () => {
const step = new FunctionFileStep();
const mapping = (step as any).csTemplateFileName;
expect(Object.keys(mapping)).toHaveLength(3);
});
});

describe('shouldPrompt', () => {
it('should always return true', () => {
const step = new FunctionFileStep();
expect(step.shouldPrompt()).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class FunctionFileStep extends AzureWizardPromptStep<IProjectWizardContex
private csTemplateFileName = {
[TargetFramework.NetFx]: 'FunctionsFileNetFx',
[TargetFramework.Net8]: 'FunctionsFileNet8',
[TargetFramework.Net10]: 'FunctionsFileNet10',
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,137 +21,38 @@ import { getDebugConfigs, updateDebugConfigs } from '../../../utils/vsCodeConfig
import { getContainingWorkspace, isMultiRootWorkspace } from '../../../utils/workspace';
import { localize } from '../../../../localize';
import * as vscode from 'vscode';
import { getCustomCodeRuntime } from '../../../utils/debug';
import { createCsFile, createProgramFile, createRulesFiles, createCsprojFile } from '../../../utils/functionProjectFiles';

/**
* This class represents a prompt step that allows the user to set up an Azure Function project.
*/
export class CreateFunctionAppFiles {
// Hide the step count in the wizard UI
public hideStepCount = true;

private csTemplateFileName = {
[TargetFramework.NetFx]: 'FunctionsFileNetFx',
[TargetFramework.Net8]: 'FunctionsFileNet8',
[ProjectType.rulesEngine]: 'RulesFunctionsFile',
};

private csprojTemplateFileName = {
[TargetFramework.NetFx]: 'FunctionsProjNetFx',
[TargetFramework.Net8]: 'FunctionsProjNet8New',
[ProjectType.rulesEngine]: 'RulesFunctionsProj',
};

private templateFolderName = {
[ProjectType.customCode]: 'FunctionProjectTemplate',
[ProjectType.rulesEngine]: 'RuleSetProjectTemplate',
};

/**
* Prompts the user to set up an Azure Function project.
* Sets up an Azure Function project by creating all necessary files.
* @param context The project wizard context.
*/
public async setup(context: IProjectWizardContext): Promise<void> {
// Set the functionAppName and namespaceName properties from the context wizard
const functionAppName = context.functionAppName;
const namespace = context.functionAppNamespace;
const targetFramework = context.targetFramework;
const { functionAppName, functionAppNamespace: namespace, targetFramework, projectType } = context;
const logicAppName = context.logicAppName || 'LogicApp';
// const funcVersion = context.version ?? (await tryGetLocalFuncVersion());

// Define the functions folder path using the context property of the wizard
const functionFolderPath = path.join(context.workspacePath, context.functionFolderName);
await fs.ensureDir(functionFolderPath);

// Define the type of project in the workspace
const projectType = context.projectType;
const assetsPath = path.join(__dirname, assetsFolderName);

// Create the .cs file inside the functions folder
await this.createCsFile(functionFolderPath, functionAppName, namespace, projectType, targetFramework);
await fs.ensureDir(functionFolderPath);
await createCsFile(assetsPath, functionFolderPath, functionAppName, namespace, projectType, targetFramework);
await createProgramFile(assetsPath, functionFolderPath, namespace, projectType, targetFramework);

// Create the .cs files inside the functions folders for rule code projects
if (projectType === ProjectType.rulesEngine) {
await this.createRulesFiles(functionFolderPath);
await createRulesFiles(assetsPath, functionFolderPath);
}

// Create the .csproj file inside the functions folder
await this.createCsprojFile(functionFolderPath, functionAppName, logicAppName, projectType, targetFramework);

// Generate the Visual Studio Code configuration files in the specified folder.
await createCsprojFile(assetsPath, functionFolderPath, functionAppName, logicAppName, projectType, targetFramework);
await this.createVscodeConfigFiles(functionFolderPath, targetFramework);
}

/**
* Creates the .cs file inside the functions folder.
* @param functionFolderPath - The path to the functions folder.
* @param methodName - The name of the method.
* @param namespace - The name of the namespace.
* @param projectType - The workspace projet type.
* @param targetFramework - The target framework.
*/
private async createCsFile(
functionFolderPath: string,
methodName: string,
namespace: string,
projectType: ProjectType,
targetFramework: TargetFramework
): Promise<void> {
const templateFile =
projectType === ProjectType.rulesEngine ? this.csTemplateFileName[ProjectType.rulesEngine] : this.csTemplateFileName[targetFramework];
const templatePath = path.join(__dirname, assetsFolderName, this.templateFolderName[projectType], templateFile);
const templateContent = await fs.readFile(templatePath, 'utf-8');

const csFilePath = path.join(functionFolderPath, `${methodName}.cs`);
const csFileContent = templateContent.replace(/<%= methodName %>/g, methodName).replace(/<%= namespace %>/g, namespace);
await fs.writeFile(csFilePath, csFileContent);
}

/**
* Creates the rules files for the project.
* @param {string} functionFolderPath - The path of the function folder.
* @returns A promise that resolves when the rules files are created.
*/
private async createRulesFiles(functionFolderPath: string): Promise<void> {
const csTemplatePath = path.join(__dirname, assetsFolderName, 'RuleSetProjectTemplate', 'ContosoPurchase');
const csRuleSetPath = path.join(functionFolderPath, 'ContosoPurchase.cs');
await fs.copyFile(csTemplatePath, csRuleSetPath);
}

/**
* Creates a .csproj file for a specific Azure Function.
* @param functionFolderPath - The path to the folder where the .csproj file will be created.
* @param methodName - The name of the Azure Function.
* @param projectType - The workspace projet type.
* @param targetFramework - The target framework.
*/
private async createCsprojFile(
functionFolderPath: string,
methodName: string,
logicAppName: string,
projectType: ProjectType,
targetFramework: TargetFramework
): Promise<void> {
const templateFile =
projectType === ProjectType.rulesEngine
? this.csprojTemplateFileName[ProjectType.rulesEngine]
: this.csprojTemplateFileName[targetFramework];
const templatePath = path.join(__dirname, assetsFolderName, this.templateFolderName[projectType], templateFile);
const templateContent = await fs.readFile(templatePath, 'utf-8');

const csprojFilePath = path.join(functionFolderPath, `${methodName}.csproj`);
let csprojFileContent: string;
if (targetFramework === TargetFramework.Net8 && projectType === ProjectType.customCode) {
csprojFileContent = templateContent.replace(
/<LogicAppFolderToPublish>\$\(MSBuildProjectDirectory\)\\..\\LogicApp<\/LogicAppFolderToPublish>/g,
`<LogicAppFolderToPublish>$(MSBuildProjectDirectory)\\..\\${logicAppName}</LogicAppFolderToPublish>`
);
} else {
csprojFileContent = templateContent.replace(
/<LogicAppFolder>LogicApp<\/LogicAppFolder>/g,
`<LogicAppFolder>${logicAppName}</LogicAppFolder>`
);
}
await fs.writeFile(csprojFilePath, csprojFileContent);
}

/**
* Creates the Visual Studio Code configuration files in the .vscode folder of the specified functions app.
* @param functionFolderPath The path to the functions folder.
Expand Down Expand Up @@ -232,7 +133,7 @@ export class CreateFunctionAppFiles {
if (debugConfig.type === 'logicapp') {
return {
...debugConfig,
customCodeRuntime: targetFramework === TargetFramework.Net8 ? 'coreclr' : 'clr',
customCodeRuntime: getCustomCodeRuntime(targetFramework),
isCodeless: true,
};
}
Expand All @@ -244,7 +145,7 @@ export class CreateFunctionAppFiles {
type: 'logicapp',
request: 'launch',
funcRuntime: funcVersion === FuncVersion.v1 ? 'clr' : 'coreclr',
customCodeRuntime: targetFramework === TargetFramework.Net8 ? 'coreclr' : 'clr',
customCodeRuntime: getCustomCodeRuntime(targetFramework),
isCodeless: true,
},
...debugConfigs.filter(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { latestGAVersion, ProjectLanguage, ProjectType, TargetFramework } from '@microsoft/vscode-extension-logic-apps';
import type { ILaunchJson, ISettingToAdd, IWebviewProjectContext } from '@microsoft/vscode-extension-logic-apps';
import { latestGAVersion, ProjectLanguage, ProjectType } from '@microsoft/vscode-extension-logic-apps';
import type { ILaunchJson, ISettingToAdd, IWebviewProjectContext, TargetFramework } from '@microsoft/vscode-extension-logic-apps';
import {
assetsFolderName,
containerTemplatesFolderName,
Expand All @@ -24,20 +24,22 @@ import { confirmEditJsonFile } from '../../../utils/fs';
import type { IActionContext } from '@microsoft/vscode-azext-utils';
import { localize } from '../../../../localize';
import { ext } from '../../../../extensionVariables';
import { getCustomCodeRuntime } from '../../../utils/debug';
import { isDebugConfigEqual } from '../../../utils/vsCodeConfig/launch';

export async function writeSettingsJson(
context: IWebviewProjectContext,
additionalSettings: ISettingToAdd[],
vscodePath: string
): Promise<void> {
const settings: ISettingToAdd[] = additionalSettings.concat(
const settings: ISettingToAdd[] = [
...additionalSettings,
{ key: projectLanguageSetting, value: ProjectLanguage.JavaScript },
{ key: funcVersionSetting, value: latestGAVersion },
// We want the terminal to open after F5, not the debug console because HTTP triggers are printed in the terminal.
{ prefix: 'debug', key: 'internalConsoleOptions', value: 'neverOpen' },
{ prefix: 'azureFunctions', key: 'suppressProject', value: true }
);
{ prefix: 'azureFunctions', key: 'suppressProject', value: true },
];

const settingsJsonPath: string = path.join(vscodePath, settingsFileName);
await confirmEditJsonFile(context, settingsJsonPath, (data: Record<string, any>): Record<string, any> => {
Expand Down Expand Up @@ -76,7 +78,7 @@ export function getDebugConfiguration(logicAppName: string, customCodeTargetFram
type: 'logicapp',
request: 'launch',
funcRuntime: 'coreclr',
customCodeRuntime: customCodeTargetFramework === TargetFramework.Net8 ? 'coreclr' : 'clr',
customCodeRuntime: getCustomCodeRuntime(customCodeTargetFramework),
isCodeless: true,
};
}
Expand Down Expand Up @@ -107,12 +109,8 @@ export async function writeLaunchJson(
}

export function insertLaunchConfig(existingConfigs: DebugConfiguration[] | undefined, newConfig: DebugConfiguration): DebugConfiguration[] {
// tslint:disable-next-line: strict-boolean-expressions
existingConfigs = existingConfigs || [];
// Remove configs that match the one we're about to add
existingConfigs = existingConfigs.filter((l1) => !isDebugConfigEqual(l1, newConfig));
existingConfigs.push(newConfig);
return existingConfigs;
const configs = (existingConfigs ?? []).filter((existingConfig) => !isDebugConfigEqual(existingConfig, newConfig));
return [...configs, newConfig];
}

export async function createLogicAppVsCodeContents(
Expand Down
Loading
Loading