Skip to content

Commit

Permalink
feat(vscode): Introduce .NET 8 to custom code and extension dependenc…
Browse files Browse the repository at this point in the history
…ies (#4947)

* Add validation for multiple dotnet versions

* Add comments and return in the await

* Fix command execution

* Add version to switch to nuget based

* Add code to differentiate .net7 and netfx

* Add .NET8 files and creation file decision

* Update to function

* Add selfContained

* Remove .NET 6

* Move target framework step after workspace type step

* Remove nuget config file

* Upgrade packages

* Add new dotnetMulti prop for manifest

* Update pack command to build all projects for extension

* Add comments and remove extra wizard context property
  • Loading branch information
ccastrotrejo committed Jun 7, 2024
1 parent 33d2700 commit 4e14837
Show file tree
Hide file tree
Showing 25 changed files with 448 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { InitDataMapperApiService, defaultDataMapperApiServiceOptions, getFuncti
import { Theme as ThemeType } from '@microsoft/logic-apps-shared';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IFileSysTreeItem } from '@microsoft/logic-apps-data-mapper-v2/src/models/Tree';
import type { IFileSysTreeItem } from '@microsoft/logic-apps-data-mapper-v2/src/models/Tree';

const mockFileItems: IFileSysTreeItem[] = [
{
Expand All @@ -41,7 +41,6 @@ const mockFileItems: IFileSysTreeItem[] = [
},
];


const customXsltPath = ['folder/file.xslt', 'file2.xslt'];

export const DataMapperStandaloneDesignerV2 = () => {
Expand Down Expand Up @@ -118,7 +117,11 @@ export const DataMapperStandaloneDesignerV2 = () => {
fetchedFunctions={fetchedFunctions}
theme={theme}
>
<DataMapperDesignerV2 saveMapDefinitionCall={saveMapDefinitionCall} saveXsltCall={saveXsltCall} readCurrentSchemaOptions={() => null}/>
<DataMapperDesignerV2
saveMapDefinitionCall={saveMapDefinitionCall}
saveXsltCall={saveXsltCall}
readCurrentSchemaOptions={() => null}
/>
</DataMapDataProviderV2>
</DataMapperDesignerProviderV2>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,13 @@ export async function validateAndInstallBinaries(context: IActionContext) {
context.telemetry.properties.lastStep = 'validateDotNetIsLatest';
await runWithDurationTelemetry(context, 'azureLogicAppsStandard.validateDotNetIsLatest', async () => {
progress.report({ increment: 20, message: '.NET SDK' });
const dotnetDependencies = dependenciesVersions?.dotnetMulti ?? dependenciesVersions?.dotnet;
await timeout(
validateDotNetIsLatest,
'.NET SDK',
dependencyTimeout,
'https://dotnet.microsoft.com/en-us/download/dotnet/6.0',
dependenciesVersions?.dotnet
'https://dotnet.microsoft.com/en-us/download/dotnet',
dotnetDependencies
);
await setDotNetCommand(context);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { FunctionConfigFile } from './FunctionConfigFile';
import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils';
import type { IProjectWizardContext } from '@microsoft/vscode-extension-logic-apps';
import { ProjectType } from '@microsoft/vscode-extension-logic-apps';
import { TargetFramework, ProjectType } from '@microsoft/vscode-extension-logic-apps';
import * as fs from 'fs-extra';
import * as path from 'path';

Expand All @@ -17,12 +17,14 @@ export class InvokeFunctionProjectSetup extends AzureWizardPromptStep<IProjectWi
public hideStepCount = true;

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

private templateFileName = {
[ProjectType.customCode]: 'FunctionsProj',
[TargetFramework.NetFx]: 'FunctionsProjNetFx',
[TargetFramework.Net8]: 'FunctionsProjNet8',
[ProjectType.rulesEngine]: 'RulesFunctionsProj',
};

Expand All @@ -39,6 +41,7 @@ export class InvokeFunctionProjectSetup extends AzureWizardPromptStep<IProjectWi
// Set the methodName and namespaceName properties from the context wizard
const methodName = context.methodName;
const namespace = context.namespaceName;
const targetFramework = context.targetFramework;

// Define the functions folder path using the context property of the wizard
const functionFolderPath = context.functionFolderPath;
Expand All @@ -47,13 +50,13 @@ export class InvokeFunctionProjectSetup extends AzureWizardPromptStep<IProjectWi
const projectType = context.projectType;

// Create the .cs file inside the functions folder
await this.createCsFile(functionFolderPath, methodName, namespace, projectType);
await this.createCsFile(functionFolderPath, methodName, namespace, projectType, targetFramework);

// Create the .cs files inside the functions folders for rule code projects
await this.createRulesFiles(functionFolderPath, projectType);

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

// Generate the Visual Studio Code configuration files in the specified folder.
const createConfigFiles = new FunctionConfigFile();
Expand All @@ -74,14 +77,22 @@ export class InvokeFunctionProjectSetup extends AzureWizardPromptStep<IProjectWi
* @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): Promise<void> {
const templatePath = path.join(__dirname, 'assets', this.templateFolderName[projectType], this.csFileName[projectType]);
private async createCsFile(
functionFolderPath: string,
methodName: string,
namespace: string,
projectType: ProjectType,
targetFramework: TargetFramework
): Promise<void> {
const templateFile =
projectType === ProjectType.rulesEngine ? this.csFileName[ProjectType.rulesEngine] : this.csFileName[targetFramework];
const templatePath = path.join(__dirname, 'assets', 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);
}

Expand All @@ -90,9 +101,17 @@ export class InvokeFunctionProjectSetup extends AzureWizardPromptStep<IProjectWi
* @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, projectType: ProjectType): Promise<void> {
const templatePath = path.join(__dirname, 'assets', this.templateFolderName[projectType], this.templateFileName[projectType]);
private async createCsprojFile(
functionFolderPath: string,
methodName: string,
projectType: ProjectType,
targetFramework: TargetFramework
): Promise<void> {
const templateFile =
projectType === ProjectType.rulesEngine ? this.templateFileName[ProjectType.rulesEngine] : this.templateFileName[targetFramework];
const templatePath = path.join(__dirname, 'assets', this.templateFolderName[projectType], templateFile);
const templateContent = await fs.readFile(templatePath, 'utf-8');

const csprojFilePath = path.join(functionFolderPath, `${methodName}.csproj`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { type IProjectWizardContext, TargetFramework, ProjectType } from '@microsoft/vscode-extension-logic-apps';
import { localize } from '../../../../../localize';
import { AzureWizardPromptStep, type IAzureQuickPickItem } from '@microsoft/vscode-azext-utils';
import { Platform } from '../../../../../constants';

/**
* Represents a step in the project creation wizard for selecting the target framework.
*/
export class TargetFrameworkStep extends AzureWizardPromptStep<IProjectWizardContext> {
public hideStepCount = true;

/**
* Prompts the user to select a target framework.
* @param {IProjectWizardContext} context - The project wizard context.
*/
public async prompt(context: IProjectWizardContext): Promise<void> {
const placeHolder: string = localize('selectTargetFramework', 'Select a target framework.');
const picks: IAzureQuickPickItem<TargetFramework>[] = [{ label: localize('Net8', '.NET 8'), data: TargetFramework.Net8 }];
if (process.platform === Platform.windows) {
picks.unshift({ label: localize('NetFx', '.NET Framework'), data: TargetFramework.NetFx });
}
context.targetFramework = (await context.ui.showQuickPick(picks, { placeHolder })).data;
}

/**
* Determines whether this step should be prompted based on the project wizard context.
* @param {IProjectWizardContext} context - The project wizard context.
* @returns True if this step should be prompted, false otherwise.
*/
public shouldPrompt(context: IProjectWizardContext): boolean {
return context.projectType === ProjectType.customCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
import * as fse from 'fs-extra';
import * as path from 'path';
import { window } from 'vscode';
import { TargetFrameworkStep } from './createCodeProjectSteps/createFunction/TargetFrameworkStep';

export async function createNewCodeProjectFromCommand(
context: IActionContext,
Expand Down Expand Up @@ -86,6 +87,7 @@ export async function createNewCodeProjectInternal(context: IActionContext, opti
new FolderListStep(),
new setWorkspaceName(),
new SetLogicAppType(),
new TargetFrameworkStep(),
new SetLogicAppName(),
new NewCodeProjectTypeStep(options.templateId, options.functionSettings),
new OpenBehaviorStep(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isNullOrUndefined } from '@microsoft/logic-apps-shared';
import { dotnetDependencyName } from '../../../constants';
import { localize } from '../../../localize';
import { binariesExist, getLatestDotNetVersion } from '../../utils/binaries';
Expand All @@ -17,44 +18,52 @@ export async function validateDotNetIsLatest(majorVersion?: string): Promise<voi
await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateDotNetIsLatest', async (context: IActionContext) => {
context.errorHandling.suppressDisplay = true;
context.telemetry.properties.isActivationEvent = 'true';
const majorVersions = majorVersion.split(',');

const showDotNetWarningKey = 'showDotNetWarning';
const showDotNetWarning = !!getWorkspaceSetting<boolean>(showDotNetWarningKey);
const binaries = binariesExist(dotnetDependencyName);
context.telemetry.properties.binariesExist = `${binaries}`;

if (!binaries) {
await installDotNet(context, majorVersion);
context.telemetry.properties.binaryCommand = `${getDotNetCommand()}`;
for (const version of majorVersions) {
await installDotNet(context, version);
}
} else if (showDotNetWarning) {
context.telemetry.properties.binaryCommand = `${getDotNetCommand()}`;
const localVersion: string | null = await getLocalDotNetVersionFromBinaries();
context.telemetry.properties.localVersion = localVersion;
const newestVersion: string | undefined = await getLatestDotNetVersion(context, majorVersion);
if (semver.major(newestVersion) === semver.major(localVersion) && semver.gt(newestVersion, localVersion)) {
context.telemetry.properties.outOfDateDotNet = 'true';
const message: string = localize(
'outdatedDotNetRuntime',
'Update your local .NET SDK version ({0}) to the latest version ({1}) for the best experience.',
localVersion,
newestVersion
);
const update: MessageItem = { title: 'Update' };
let result: MessageItem;
do {
result =
newestVersion !== undefined
? await context.ui.showWarningMessage(message, update, DialogResponses.learnMore, DialogResponses.dontWarnAgain)
: await context.ui.showWarningMessage(message, DialogResponses.learnMore, DialogResponses.dontWarnAgain);
if (result === DialogResponses.learnMore) {
await openUrl('https://dotnet.microsoft.com/en-us/download/dotnet/6.0');
} else if (result === update) {
await installDotNet(context, majorVersion);
} else if (result === DialogResponses.dontWarnAgain) {
await updateGlobalSetting(showDotNetWarningKey, false);
for (const version of majorVersions) {
const localVersion: string | null = await getLocalDotNetVersionFromBinaries(version);
if (isNullOrUndefined(localVersion)) {
await installDotNet(context, version);
} else {
context.telemetry.properties.localVersion = localVersion;
const newestVersion: string | undefined = await getLatestDotNetVersion(context, version);
if (semver.major(newestVersion) === semver.major(localVersion) && semver.gt(newestVersion, localVersion)) {
context.telemetry.properties.outOfDateDotNet = 'true';
const message: string = localize(
'outdatedDotNetRuntime',
'Update your local .NET SDK version ({0}) to the latest version ({1}) for the best experience.',
localVersion,
newestVersion
);
const update: MessageItem = { title: 'Update' };
let result: MessageItem;
do {
result =
newestVersion !== undefined
? await context.ui.showWarningMessage(message, update, DialogResponses.learnMore, DialogResponses.dontWarnAgain)
: await context.ui.showWarningMessage(message, DialogResponses.learnMore, DialogResponses.dontWarnAgain);
if (result === DialogResponses.learnMore) {
await openUrl(`https://dotnet.microsoft.com/en-us/download/dotnet/${version}`);
} else if (result === update) {
await installDotNet(context, version);
} else if (result === DialogResponses.dontWarnAgain) {
await updateGlobalSetting(showDotNetWarningKey, false);
}
} while (result === DialogResponses.learnMore);
}
} while (result === DialogResponses.learnMore);
}
}
}
context.telemetry.properties.binaryCommand = `${getDotNetCommand()}`;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function switchToDotnetProject(context: IProjectWizardContext, targ
const projTemplateKey = await getTemplateKeyFromProjFile(context, projectPath, version, ProjectLanguage.CSharp);
const dotnetVersion = await getFramework(context, projectPath);
const useBinaries = useBinariesDependencies();
const dotnetLocalVersion = useBinaries ? await getLocalDotNetVersionFromBinaries() : '';
const dotnetLocalVersion = useBinaries ? await getLocalDotNetVersionFromBinaries('6') : '';

await deleteBundleProjectFiles(target);
await renameBundleProjectFiles(target);
Expand Down
29 changes: 24 additions & 5 deletions apps/vs-code-designer/src/app/utils/binaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import * as vscode from 'vscode';

import AdmZip = require('adm-zip');
import request = require('request');
import { isNullOrUndefined } from '@microsoft/logic-apps-shared';

/**
* Download and Extracts dependency zip.
Expand Down Expand Up @@ -154,20 +155,32 @@ export async function getLatestFunctionCoreToolsVersion(context: IActionContext,
return DependencyVersion.funcCoreTools;
}

/**
* Retrieves the latest version of .NET SDK.
* @param {IActionContext} context - The action context.
* @param {string} majorVersion - The major version of .NET SDK to retrieve. (optional)
* @returns A promise that resolves to the latest version of .NET SDK.
* @throws An error if there is an issue retrieving the latest .NET SDK version.
*/
export async function getLatestDotNetVersion(context: IActionContext, majorVersion?: string): Promise<string> {
context.telemetry.properties.dotNetMajorVersion = majorVersion;

if (majorVersion) {
await readJsonFromUrl('https://api.github.com/repos/dotnet/sdk/releases')
return await readJsonFromUrl('https://api.github.com/repos/dotnet/sdk/releases')
.then((response: IGitHubReleaseInfo[]) => {
context.telemetry.properties.latestVersionSource = 'github';
response.forEach((releaseInfo: IGitHubReleaseInfo) => {
let latestVersion: string | null;
for (const releaseInfo of response) {
const releaseVersion: string | null = semver.valid(semver.coerce(releaseInfo.tag_name));
context.telemetry.properties.latestGithubVersion = releaseInfo.tag_name;
if (checkMajorVersion(releaseVersion, majorVersion)) {
return releaseVersion;
if (
checkMajorVersion(releaseVersion, majorVersion) &&
(isNullOrUndefined(latestVersion) || semver.gt(releaseVersion, latestVersion))
) {
latestVersion = releaseVersion;
}
});
}
return latestVersion;
})
.catch((error) => {
throw Error(localize('errorNewestDotNetVersion', `Error getting latest .NET SDK version: ${error}`));
Expand Down Expand Up @@ -299,6 +312,12 @@ async function extractDependency(dependencyFilePath: string, targetFolder: strin
}
}

/**
* Checks if the major version of a given version string matches the specified major version.
* @param {string} version - The version string to check.
* @param {string} majorVersion - The major version to compare against.
* @returns A boolean indicating whether the major version matches.
*/
function checkMajorVersion(version: string, majorVersion: string): boolean {
return semver.major(version) === Number(majorVersion);
}
Expand Down
Loading

0 comments on commit 4e14837

Please sign in to comment.