diff --git a/apps/vs-code-designer/package.json b/apps/vs-code-designer/package.json index a118d22eaea..86146992c85 100644 --- a/apps/vs-code-designer/package.json +++ b/apps/vs-code-designer/package.json @@ -57,13 +57,14 @@ "scripts": { "build:extension": "tsup && pnpm run copyFiles", "build:ui": "tsup --config tsup.e2e.test.config.ts", - "copyFiles": "node extension-copy-svgs.js", + "copyFiles": "node scripts/extension-copy-svgs.js", "vscode:designer:pack": "pnpm run vscode:designer:pack:step1 && pnpm run vscode:designer:pack:step2", "vscode:designer:pack:step1": "cd ./dist && npm install", "vscode:designer:pack:step2": "cd ./dist && vsce package", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "test:extension-unit": "vitest run --retry=3", "vscode:designer:e2e:ui": "pnpm run build:ui && cd dist && extest setup-and-run ../out/test/**/*.js --coverage", - "vscode:designer:e2e:headless": "pnpm run build:ui && cd dist && extest setup-and-run ../out/test/**/*.js --coverage" + "vscode:designer:e2e:headless": "pnpm run build:ui && cd dist && extest setup-and-run ../out/test/**/*.js --coverage", + "update:extension-bundle-version": "node scripts/update-extension-bundle-version.js" } } diff --git a/apps/vs-code-designer/extension-copy-svgs.js b/apps/vs-code-designer/scripts/extension-copy-svgs.js similarity index 89% rename from apps/vs-code-designer/extension-copy-svgs.js rename to apps/vs-code-designer/scripts/extension-copy-svgs.js index 7fa2bdcc4a6..df14d45bde3 100644 --- a/apps/vs-code-designer/extension-copy-svgs.js +++ b/apps/vs-code-designer/scripts/extension-copy-svgs.js @@ -6,7 +6,7 @@ const copyDoc = async (projectPath) => { await copy('./src', `${projectPath}`, { filter: ['LICENSE.md', 'package.json', 'README.md', 'assets/**'], }); - await copy(path.resolve(__dirname, '..', '..'), `${projectPath}`, { + await copy(path.resolve(__dirname, '..'), `${projectPath}`, { filter: ['CHANGELOG.md'], }); }; diff --git a/apps/vs-code-designer/scripts/update-extension-bundle-version.js b/apps/vs-code-designer/scripts/update-extension-bundle-version.js new file mode 100644 index 00000000000..cecb76a63a0 --- /dev/null +++ b/apps/vs-code-designer/scripts/update-extension-bundle-version.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node +/* eslint-disable no-undef */ +const fs = require('fs/promises'); +const path = require('path'); + +async function main() { + const version = process.argv[2]; + if (!version) { + console.error('Usage: node scripts/update-extension-bundle-version.js '); + process.exitCode = 1; + return; + } + + const constantsPath = path.resolve(__dirname, '../src/constants.ts'); + const dockerfilePath = path.resolve(__dirname, '../src/container/Dockerfile'); + + await updateFile( + constantsPath, + /export const EXTENSION_BUNDLE_VERSION = ['"][^'"]+['"];\s*/, + `export const EXTENSION_BUNDLE_VERSION = '${version}';\n` + ); + await updateFile(dockerfilePath, /ARG EXTENSION_BUNDLE_VERSION=[^\s]+/, `ARG EXTENSION_BUNDLE_VERSION=${version}`); + + console.log(`Updated extension bundle version to ${version}`); +} + +async function updateFile(filePath, regex, replacement) { + const original = await fs.readFile(filePath, 'utf8'); + if (!regex.test(original)) { + throw new Error(`Could not find target pattern in ${filePath}`); + } + const updated = original.replace(regex, replacement); + if (updated !== original) { + await fs.writeFile(filePath, updated); + } +} + +main().catch((err) => { + console.error(err.message || err); + process.exitCode = 1; +}); diff --git a/apps/vs-code-designer/src/app/commands/binaries/resetValidateAndInstallBinaries.ts b/apps/vs-code-designer/src/app/commands/binaries/resetValidateAndInstallBinaries.ts deleted file mode 100644 index 85c05faf0d2..00000000000 --- a/apps/vs-code-designer/src/app/commands/binaries/resetValidateAndInstallBinaries.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { - autoRuntimeDependenciesPathSettingKey, - autoRuntimeDependenciesValidationAndInstallationSetting, - dotNetBinaryPathSettingKey, - funcCoreToolsBinaryPathSettingKey, - nodeJsBinaryPathSettingKey, -} from '../../../constants'; -import { localize } from '../../../localize'; -import { updateGlobalSetting } from '../../utils/vsCodeConfig/settings'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; - -/** - * Resets the auto validation and installation of binaries dependencies. - * @param {IActionContext} context The action context. - */ -export async function resetValidateAndInstallBinaries(context: IActionContext): Promise { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, // Location of the progress indicator - title: localize('resetBinariesDependencies', 'Resetting binaries dependencies settings'), // Title displayed in the progress notification - cancellable: false, // Allow the user to cancel the task - }, - async (progress) => { - await updateGlobalSetting(autoRuntimeDependenciesValidationAndInstallationSetting, true); - progress.report({ increment: 20, message: localize('resetValidation', 'Reset auto runtime validation and installation') }); - await resetBinariesPathSettings(progress); - context.telemetry.properties.resetBinariesDependencies = 'true'; - } - ); -} - -/** - * Disables the auto validation and installation of binaries dependencies. - * @param {IActionContext} context The action context. - */ -export async function disableValidateAndInstallBinaries(context: IActionContext): Promise { - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, // Location of the progress indicator - title: localize('disableBinariesDependencies', 'Disabling binaries dependencies settings'), // Title displayed in the progress notification - cancellable: false, // Allow the user to cancel the task - }, - async (progress) => { - await updateGlobalSetting(autoRuntimeDependenciesValidationAndInstallationSetting, false); - progress.report({ increment: 20, message: localize('disableValidation', 'Disable auto runtime validation and installation') }); - await resetBinariesPathSettings(progress); - context.telemetry.properties.disableBinariesDependencies = 'true'; - } - ); -} - -/** - * Resets the path settings for auto runtime dependencies, dotnet binary, node js binary, and func core tools binary. - * @param {vscode.Progress} progress - The progress object to report the progress of the reset operation. - */ -async function resetBinariesPathSettings(progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { - await updateGlobalSetting(autoRuntimeDependenciesPathSettingKey, undefined); - progress.report({ increment: 40, message: localize('resetDependenciesPath', 'Reset auto runtime dependencies path') }); - - await updateGlobalSetting(dotNetBinaryPathSettingKey, undefined); - progress.report({ increment: 60, message: localize('resetDotnet', 'Reset dotnet binary path') }); - - await updateGlobalSetting(nodeJsBinaryPathSettingKey, undefined); - progress.report({ increment: 80, message: localize('resetNodeJs', 'Reset node js binary path') }); - - await updateGlobalSetting(funcCoreToolsBinaryPathSettingKey, undefined); - progress.report({ increment: 100, message: localize('resetFuncCoreTools', 'Reset func core tools binary path') }); -} diff --git a/apps/vs-code-designer/src/app/commands/binaries/validateAndInstallBinaries.ts b/apps/vs-code-designer/src/app/commands/binaries/validateAndInstallBinaries.ts deleted file mode 100644 index c72accda5ec..00000000000 --- a/apps/vs-code-designer/src/app/commands/binaries/validateAndInstallBinaries.ts +++ /dev/null @@ -1,122 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { autoRuntimeDependenciesPathSettingKey, defaultDependencyPathValue } from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { localize } from '../../../localize'; -import { getDependencyTimeout } from '../../utils/binaries'; -import { getDependenciesVersion } from '../../utils/bundleFeed'; -import { setDotNetCommand } from '../../utils/dotnet/dotnet'; -import { executeCommand } from '../../utils/funcCoreTools/cpUtils'; -import { setFunctionsCommand } from '../../utils/funcCoreTools/funcVersion'; -import { setNodeJsCommand } from '../../utils/nodeJs/nodeJsVersion'; -import { runWithDurationTelemetry } from '../../utils/telemetry'; -import { timeout } from '../../utils/timeout'; -import { getGlobalSetting, updateGlobalSetting } from '../../utils/vsCodeConfig/settings'; -import { validateDotNetIsLatest } from '../dotnet/validateDotNetIsLatest'; -import { validateFuncCoreToolsIsLatest } from '../funcCoreTools/validateFuncCoreToolsIsLatest'; -import { validateNodeJsIsLatest } from '../nodeJs/validateNodeJsIsLatest'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { IBundleDependencyFeed } from '@microsoft/vscode-extension-logic-apps'; -import * as vscode from 'vscode'; - -export async function validateAndInstallBinaries(context: IActionContext) { - const helpLink = 'https://aka.ms/lastandard/onboarding/troubleshoot'; - - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, // Location of the progress indicator - title: localize('validateRuntimeDependency', 'Validating Runtime Dependency'), // Title displayed in the progress notification - cancellable: false, // Allow the user to cancel the task - }, - async (progress, token) => { - token.onCancellationRequested(() => { - // Handle cancellation logic - executeCommand(ext.outputChannel, undefined, 'echo', 'validateAndInstallBinaries was canceled'); - }); - - context.telemetry.properties.lastStep = 'getGlobalSetting'; - progress.report({ increment: 10, message: 'Get Settings' }); - - const dependencyTimeout = getDependencyTimeout() * 1000; - - context.telemetry.properties.dependencyTimeout = `${dependencyTimeout} milliseconds`; - if (!getGlobalSetting(autoRuntimeDependenciesPathSettingKey)) { - await updateGlobalSetting(autoRuntimeDependenciesPathSettingKey, defaultDependencyPathValue); - context.telemetry.properties.dependencyPath = defaultDependencyPathValue; - } - - context.telemetry.properties.lastStep = 'getDependenciesVersion'; - progress.report({ increment: 10, message: 'Get dependency version from CDN' }); - let dependenciesVersions: IBundleDependencyFeed; - try { - dependenciesVersions = await getDependenciesVersion(context); - context.telemetry.properties.dependenciesVersions = JSON.stringify(dependenciesVersions); - } catch (error) { - // Unable to get dependency.json, will default to fallback versions - console.log(error); - } - - context.telemetry.properties.lastStep = 'validateNodeJsIsLatest'; - - try { - await runWithDurationTelemetry(context, 'azureLogicAppsStandard.validateNodeJsIsLatest', async () => { - progress.report({ increment: 20, message: 'NodeJS' }); - await timeout( - validateNodeJsIsLatest, - 'NodeJs', - dependencyTimeout, - 'https://github.com/nodesource/distributions', - dependenciesVersions?.nodejs - ); - await setNodeJsCommand(); - }); - - context.telemetry.properties.lastStep = 'validateFuncCoreToolsIsLatest'; - await runWithDurationTelemetry(context, 'azureLogicAppsStandard.validateFuncCoreToolsIsLatest', async () => { - progress.report({ increment: 20, message: 'Functions Runtime' }); - await timeout( - validateFuncCoreToolsIsLatest, - 'Functions Runtime', - dependencyTimeout, - 'https://github.com/Azure/azure-functions-core-tools/releases', - dependenciesVersions?.funcCoreTools - ); - await setFunctionsCommand(); - }); - - context.telemetry.properties.lastStep = 'validateDotNetIsLatest'; - await runWithDurationTelemetry(context, 'azureLogicAppsStandard.validateDotNetIsLatest', async () => { - progress.report({ increment: 20, message: '.NET SDK' }); - const dotnetDependencies = dependenciesVersions?.dotnetVersions ?? dependenciesVersions?.dotnet; - await timeout( - validateDotNetIsLatest, - '.NET SDK', - dependencyTimeout, - 'https://dotnet.microsoft.com/en-us/download/dotnet', - dotnetDependencies - ); - await setDotNetCommand(); - }); - ext.outputChannel.appendLog( - localize( - 'azureLogicApsBinariesSucessfull', - 'Azure Logic Apps Standard Runtime Dependencies validation and installation completed successfully.' - ) - ); - } catch (error) { - ext.outputChannel.appendLog( - localize('azureLogicApsBinariesError', 'Error in dependencies validation and installation: "{0}"...', error?.message) - ); - context.telemetry.properties.dependenciesError = error?.message; - vscode.window.showErrorMessage( - localize( - 'binariesTroubleshoot', - `The Validation and Installation of Runtime Dependencies encountered an error. To resolve this issue, please click [here](${helpLink}) to access our troubleshooting documentation for step-by-step instructions.` - ) - ); - } - } - ); -} diff --git a/apps/vs-code-designer/src/app/commands/cloudToLocal/cloudToLocal.ts b/apps/vs-code-designer/src/app/commands/cloudToLocal/cloudToLocal.ts index f2bceffc183..f1461fef4cd 100644 --- a/apps/vs-code-designer/src/app/commands/cloudToLocal/cloudToLocal.ts +++ b/apps/vs-code-designer/src/app/commands/cloudToLocal/cloudToLocal.ts @@ -10,7 +10,7 @@ import { LogicAppNameStep } from '../createProject/createProjectSteps/logicAppNa import { WorkspaceNameStep } from '../createWorkspace/createWorkspaceSteps/workspaceNameStep'; import { AzureWizard } from '@microsoft/vscode-azext-utils'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import { latestGAVersion, OpenBehavior } from '@microsoft/vscode-extension-logic-apps'; +import { latestGAVersion } from '@microsoft/vscode-extension-logic-apps'; import type { ICreateFunctionOptions, IFunctionWizardContext, ProjectLanguage } from '@microsoft/vscode-extension-logic-apps'; import { ProcessPackageStep } from './cloudToLocalSteps/processPackageStep'; import { SelectFolderForNewWorkspaceStep } from './cloudToLocalSteps/selectFolderForNewWorkspaceStep'; @@ -43,9 +43,7 @@ export async function cloudToLocal( projectPath: options.folderPath, }); - if (options.suppressOpenFolder) { - wizardContext.openBehavior = OpenBehavior.dontOpen; - } else if (!wizardContext.openBehavior) { + if (!wizardContext.openBehavior) { wizardContext.openBehavior = getWorkspaceSetting(projectOpenBehaviorSetting); context.telemetry.properties.openBehaviorFromSetting = String(!!wizardContext.openBehavior); } @@ -57,6 +55,7 @@ export async function cloudToLocal( // TODO(aeldridge): Can we just use WorkspaceFolderStep instead? new SelectFolderForNewWorkspaceStep(), new WorkspaceNameStep(), + // new DevcontainerStep(), new LogicAppNameStep(), await ProjectTypeStep.create(context, options.templateId, options.functionSettings, true), new WorkspaceSettingsStep(), diff --git a/apps/vs-code-designer/src/app/commands/convertToWorkspace.ts b/apps/vs-code-designer/src/app/commands/convertToWorkspace.ts index 96594b8723e..26e7114a2d6 100644 --- a/apps/vs-code-designer/src/app/commands/convertToWorkspace.ts +++ b/apps/vs-code-designer/src/app/commands/convertToWorkspace.ts @@ -60,7 +60,7 @@ export async function convertToWorkspace(context: IActionContext): Promise = new AzureWizard(wizardContext, { title: localize('convertToWorkspace', 'Convert to workspace'), - promptSteps: [new WorkspaceFolderStep(), new WorkspaceNameStep(), new WorkspaceFileStep()], + promptSteps: [new WorkspaceFolderStep(), new WorkspaceNameStep(), /*new DevcontainerStep(), */ new WorkspaceFileStep()], executeSteps: [new OpenFolderStep()], }); diff --git a/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicAppSteps/logicAppCreateStep.ts b/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicAppSteps/logicAppCreateStep.ts index 36c5d9c9210..5f46ea39219 100644 --- a/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicAppSteps/logicAppCreateStep.ts +++ b/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicAppSteps/logicAppCreateStep.ts @@ -30,7 +30,7 @@ import type { CustomLocation } from '@microsoft/vscode-azext-azureappservice'; import { LocationListStep } from '@microsoft/vscode-azext-azureutils'; import { AzureWizardExecuteStep, nonNullOrEmptyValue, nonNullProp } from '@microsoft/vscode-azext-utils'; import type { ILogicAppWizardContext, ConnectionStrings } from '@microsoft/vscode-extension-logic-apps'; -import { StorageOptions, FuncVersion, WorkerRuntime } from '@microsoft/vscode-extension-logic-apps'; +import { StorageOptions, WorkerRuntime } from '@microsoft/vscode-extension-logic-apps'; import type { Progress } from 'vscode'; export class LogicAppCreateStep extends AzureWizardExecuteStep { @@ -191,18 +191,7 @@ export class LogicAppCreateStep extends AzureWizardExecuteStep ): Promise { - const version: FuncVersion = nonNullProp(context, 'version'); const hostJsonPath: string = path.join(context.projectPath, hostFileName); if (await confirmOverwriteFile(context, hostJsonPath)) { - const hostJson: IHostJsonV2 | IHostJsonV1 = version === FuncVersion.v1 ? {} : await this.getHostContent(context); + const hostJson: IHostJsonV2 = await this.getHostContent(); await writeFormattedJson(hostJsonPath, hostJson); } @@ -82,7 +80,7 @@ export class ProjectCreateStep extends ProjectCreateStepBase { } } - protected async getHostContent(context: IActionContext): Promise { + protected async getHostContent(): Promise { const hostJson: IHostJsonV2 = { version: '2.0', logging: { @@ -95,7 +93,10 @@ export class ProjectCreateStep extends ProjectCreateStepBase { }, }; - await addDefaultBundle(context, hostJson); + hostJson.extensionBundle = { + id: extensionBundleId, + version: defaultVersionRange, + }; return hostJson; } diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodefulWorkflow/createCodefulWorkflowSteps/codefulWorkflowCreateStep.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodefulWorkflow/createCodefulWorkflowSteps/codefulWorkflowCreateStep.ts index e1cb5c184e6..27344c2ce26 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodefulWorkflow/createCodefulWorkflowSteps/codefulWorkflowCreateStep.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodefulWorkflow/createCodefulWorkflowSteps/codefulWorkflowCreateStep.ts @@ -28,7 +28,6 @@ import { vscodeFolderName, } from '../../../../../constants'; import { removeAppKindFromLocalSettings, setLocalAppSetting } from '../../../../utils/appSettings/localSettings'; -import { validateDotnetInstalled } from '../../../../utils/dotnet/executeDotnetTemplateCommand'; import { switchToDotnetProject } from '../../../workflows/switchToDotnetProject'; import * as vscode from 'vscode'; import { createConnectionsJson } from '../../../../utils/codeless/connection'; @@ -39,7 +38,6 @@ import { getDebugConfiguration } from '../../../../utils/debug'; export class CodefulWorkflowCreateStep extends WorkflowCreateStepBase { public async executeCore(context: IFunctionWizardContext): Promise { - await validateDotnetInstalled(context); const logicAppName = context.logicAppName || 'LogicApp'; const workflowFolderPath = path.join(context.projectPath, nonNullProp(context, 'functionName')); const workflowFilePath = path.join(workflowFolderPath, codefulWorkflowFileName); @@ -169,7 +167,7 @@ export class CodefulWorkflowCreateStep extends WorkflowCreateStepBase { const target = vscode.Uri.file(context.projectPath); - await switchToDotnetProject(context, target, '8', true); + await switchToDotnetProject(context, target, true); await this.updateHostJson(context, hostFileName); diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflow.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflow.ts index a15f2c2cfa4..f956ea28309 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflow.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflow.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { projectTemplateKeySetting } from '../../../../constants'; import { getProjFiles } from '../../../utils/dotnet/dotnet'; -import { addLocalFuncTelemetry, checkSupportedFuncVersion } from '../../../utils/funcCoreTools/funcVersion'; +import { addLocalFuncTelemetry } from '../../../utils/funcCoreTools/funcVersion'; import { verifyAndPromptToCreateProject } from '../../../utils/verifyIsProject'; import { getWorkspaceSetting } from '../../../utils/vsCodeConfig/settings'; import { verifyInitForVSCode } from '../../../utils/vsCodeConfig/verifyInitForVSCode'; @@ -55,8 +55,6 @@ export async function createCodelessWorkflow( [language, version] = await verifyInitForVSCode(context, projectPath, language, version); - checkSupportedFuncVersion(version); - const projectTemplateKey: string | undefined = getWorkspaceSetting(projectTemplateKeySetting, projectPath); const wizardContext: IFunctionWizardContext = Object.assign(context, { projectPath, diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/codelessWorkflowCreateStep.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/codelessWorkflowCreateStep.ts index 2857d9a66ce..7d4a4363ef7 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/codelessWorkflowCreateStep.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/codelessWorkflowCreateStep.ts @@ -23,10 +23,9 @@ import { updateFunctionsSDKVersion, writeBuildFileToDisk, } from '../../../../utils/codeless/updateBuildFile'; -import { getFramework, validateDotnetInstalled } from '../../../../utils/dotnet/executeDotnetTemplateCommand'; +import { getFramework } from '../../../../utils/dotnet/executeDotnetTemplateCommand'; import { writeFormattedJson } from '../../../../utils/fs'; import { WorkflowCreateStepBase } from '../../createWorkflowSteps/workflowCreateStepBase'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; import { nonNullProp } from '@microsoft/vscode-azext-utils'; import { WorkflowProjectType, MismatchBehavior } from '@microsoft/vscode-extension-logic-apps'; import type { IFunctionWizardContext, IWorkflowTemplate, IHostJsonV2, StandardApp } from '@microsoft/vscode-extension-logic-apps'; @@ -38,8 +37,7 @@ export class CodelessWorkflowCreateStep extends WorkflowCreateStepBase { - await validateDotnetInstalled(context); + public static async createStep(): Promise { return new CodelessWorkflowCreateStep(); } diff --git a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/workflowKindStep.ts b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/workflowKindStep.ts index a66710eff7e..d68c143bcd5 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/workflowKindStep.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkflow/createCodelessWorkflow/createCodelessWorkflowSteps/workflowKindStep.ts @@ -64,7 +64,7 @@ export class WorkflowKindStep extends AzureWizardPromptStep { + public hideStepCount = true; + + public shouldPrompt(): boolean { + return true; + } + + public async prompt(context: IProjectWizardContext): Promise { + await this.createDevcontainerFiles(context); + } + + private async createDevcontainerFiles(context: IProjectWizardContext): Promise { + // Resolve source directory with canonical container config. When running from compiled dist the + // source 'container' folder may live beside 'src' or only within 'src/container'. Try a set of candidates. + const candidateDirs: string[] = [path.join(ext.context.extensionPath, 'assets', 'container')]; + + let sourceContainerDir: string | undefined; + for (const dir of candidateDirs) { + if (await fs.pathExists(dir)) { + sourceContainerDir = dir; + break; + } + } + + // Create .devcontainer folder at the same level as .code-workspace file + const devcontainerPath = path.join(context.workspacePath, '.devcontainer'); + await fs.ensureDir(devcontainerPath); + + // Files we expect in the source directory + const filesToCopy = ['devcontainer.json', 'Dockerfile']; + + if (!sourceContainerDir) { + // Could not locate source directory; create marker file and return gracefully. + await fs.writeFile( + path.join(devcontainerPath, 'README.missing-devcontainer.txt'), + `Devcontainer source templates not found. Looked in:\n${candidateDirs.join('\n')}\n` + ); + return; + } + + for (const fileName of filesToCopy) { + const src = path.join(sourceContainerDir, fileName); + const dest = path.join(devcontainerPath, fileName); + try { + if (await fs.pathExists(src)) { + await fs.copyFile(src, dest); + } else { + await fs.writeFile(`${dest}.missing`, `Expected source file not found: ${src}`); + } + } catch (err) { + await fs.writeFile(`${dest}.error`, `Error copying ${fileName}: ${(err as Error).message}`); + } + } + } +} diff --git a/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openBehaviorStep.ts b/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openBehaviorStep.ts index 8570d201b3d..2d3df3a05a7 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openBehaviorStep.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openBehaviorStep.ts @@ -10,14 +10,12 @@ import { OpenBehavior } from '@microsoft/vscode-extension-logic-apps'; export class OpenBehaviorStep extends AzureWizardPromptStep { public shouldPrompt(context: IProjectWizardContext): boolean { - return !context.openBehavior && context.openBehavior !== OpenBehavior.alreadyOpen && context.openBehavior !== OpenBehavior.dontOpen; + return !context.openBehavior && context.openBehavior !== OpenBehavior.alreadyOpen; } public async prompt(context: IProjectWizardContext): Promise { const picks: IAzureQuickPickItem[] = [ - { label: localize('OpenInCurrentWindow', 'Open in current window'), data: OpenBehavior.openInCurrentWindow }, { label: localize('OpenInNewWindow', 'Open in new window'), data: OpenBehavior.openInNewWindow }, - { label: localize('AddToWorkspace', 'Add to workspace'), data: OpenBehavior.addToWorkspace }, ]; const placeHolder: string = localize('selectOpenBehavior', 'Select how you would like to open your project'); diff --git a/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openFolderStep.ts b/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openFolderStep.ts index 472b12e303e..9f92e39f7e2 100644 --- a/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openFolderStep.ts +++ b/apps/vs-code-designer/src/app/commands/createWorkspace/createWorkspaceSteps/openFolderStep.ts @@ -7,7 +7,7 @@ import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils'; import type { IProjectWizardContext } from '@microsoft/vscode-extension-logic-apps'; import { OpenBehavior } from '@microsoft/vscode-extension-logic-apps'; import * as fs from 'fs'; -import { commands, Uri, workspace } from 'vscode'; +import { commands, Uri } from 'vscode'; import { extensionCommand } from '../../../../constants'; export class OpenFolderStep extends AzureWizardExecuteStep { @@ -19,7 +19,7 @@ export class OpenFolderStep extends AzureWizardExecuteStep { - const openFolders = workspace.workspaceFolders || []; let workspaceUri: Uri; // Check if .code-workspace file exists in project path @@ -40,22 +39,11 @@ export class OpenFolderStep extends AzureWizardExecuteStep { progress.report({ message: 'Starting backend runtime, this may take a few seconds...' }); @@ -75,7 +76,7 @@ export async function startBackendRuntime(context: IActionContext, projectPath: ); const cwd: string = designTimeDirectory.fsPath; const portArgs = `--port ${designTimeInst.port}`; - startDesignTimeProcess(ext.outputChannel, cwd, getFunctionsCommand(), 'host', 'start', portArgs); + startDesignTimeProcess(ext.outputChannel, cwd, 'func', 'host', 'start', portArgs); await waitForDesignTimeStartUp(context, projectPath, url, true); } else { diff --git a/apps/vs-code-designer/src/app/commands/dotnet/installDotNet.ts b/apps/vs-code-designer/src/app/commands/dotnet/installDotNet.ts deleted file mode 100644 index f14a890055b..00000000000 --- a/apps/vs-code-designer/src/app/commands/dotnet/installDotNet.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*------------------p--------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { autoRuntimeDependenciesPathSettingKey, dotnetDependencyName } from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { downloadAndExtractDependency, getDotNetBinariesReleaseUrl } from '../../utils/binaries'; -import { getGlobalSetting } from '../../utils/vsCodeConfig/settings'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; - -export async function installDotNet(context: IActionContext, majorVersion?: string): Promise { - ext.outputChannel.show(); - context.telemetry.properties.majorVersion = majorVersion; - const targetDirectory = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - - context.telemetry.properties.lastStep = 'getDotNetBinariesReleaseUrl'; - const scriptUrl = getDotNetBinariesReleaseUrl(); - - context.telemetry.properties.lastStep = 'downloadAndExtractBinaries'; - await downloadAndExtractDependency(context, scriptUrl, targetDirectory, dotnetDependencyName, null, majorVersion); -} diff --git a/apps/vs-code-designer/src/app/commands/dotnet/validateDotNetInstalled.ts b/apps/vs-code-designer/src/app/commands/dotnet/validateDotNetInstalled.ts deleted file mode 100644 index eef69e112e3..00000000000 --- a/apps/vs-code-designer/src/app/commands/dotnet/validateDotNetInstalled.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { validateDotNetSDKSetting } from '../../../constants'; -import { localize } from '../../../localize'; -import { getDotNetCommand } from '../../utils/dotnet/dotnet'; -import { executeCommand } from '../../utils/funcCoreTools/cpUtils'; -import { getWorkspaceSetting } from '../../utils/vsCodeConfig/settings'; -import { installDotNet } from './installDotNet'; -import { callWithTelemetryAndErrorHandling, DialogResponses, openUrl } from '@microsoft/vscode-azext-utils'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { MessageItem } from 'vscode'; - -/** - * Checks if dotnet 6 is installed, and installs it if needed. - * @param {IActionContext} context - Workflow file path. - * @param {string} fsPath - Workspace file system path. - * @returns {Promise} Returns true if it is installed or was sucessfully installed, otherwise returns false. - */ -export async function validateDotNetIsInstalled(context: IActionContext, fsPath: string): Promise { - let input: MessageItem | undefined; - let installed = false; - const install: MessageItem = { title: localize('install', 'Install') }; - const message: string = localize('installDotnetSDK', 'You must have the .NET SDK installed. Would you like to install it now?'); - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateDotNetIsInstalled', async (innerContext: IActionContext) => { - innerContext.errorHandling.suppressDisplay = true; - - if (!getWorkspaceSetting(validateDotNetSDKSetting, fsPath)) { - innerContext.telemetry.properties.validateDotNet = 'false'; - installed = true; - } else if (await isDotNetInstalled()) { - installed = true; - } else { - const items: MessageItem[] = [install, DialogResponses.learnMore]; - input = await innerContext.ui.showWarningMessage(message, { modal: true }, ...items); - innerContext.telemetry.properties.dialogResult = input.title; - - if (input === install) { - await installDotNet(innerContext); - installed = true; - } else if (input === DialogResponses.learnMore) { - await openUrl('https://dotnet.microsoft.com/download/dotnet/6.0'); - } - } - }); - - // validate that DotNet was installed only if user confirmed - if (input === install && !installed) { - if ( - (await context.ui.showWarningMessage( - localize('failedInstallDotNet', 'The .NET SDK installation failed. Please manually install instead.'), - DialogResponses.learnMore - )) === DialogResponses.learnMore - ) { - await openUrl('https://dotnet.microsoft.com/download/dotnet/6.0'); - } - } - - return installed; -} - -/** - * Check is dotnet is installed. - * @returns {Promise} Returns true if installed, otherwise returns false. - */ -async function isDotNetInstalled(): Promise { - try { - await executeCommand(undefined, undefined, getDotNetCommand(), '--version'); - return true; - } catch (_error) { - return false; - } -} diff --git a/apps/vs-code-designer/src/app/commands/dotnet/validateDotNetIsLatest.ts b/apps/vs-code-designer/src/app/commands/dotnet/validateDotNetIsLatest.ts deleted file mode 100644 index af263dd69b7..00000000000 --- a/apps/vs-code-designer/src/app/commands/dotnet/validateDotNetIsLatest.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 { binariesExist, getLatestDotNetVersion } from '../../utils/binaries'; -import { getDotNetCommand, getLocalDotNetVersionFromBinaries } from '../../utils/dotnet/dotnet'; -import { installDotNet } from './installDotNet'; -import { callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import * as semver from 'semver'; - -export async function validateDotNetIsLatest(majorVersion?: string): Promise { - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateDotNetIsLatest', async (context: IActionContext) => { - context.errorHandling.suppressDisplay = true; - context.telemetry.properties.isActivationEvent = 'true'; - const majorVersions = majorVersion.split(','); - - const binaries = binariesExist(dotnetDependencyName); - context.telemetry.properties.binariesExist = `${binaries}`; - - if (binaries) { - 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'; - await installDotNet(context, version); - } - } - } - } else { - for (const version of majorVersions) { - await installDotNet(context, version); - } - } - context.telemetry.properties.binaryCommand = `${getDotNetCommand()}`; - }); -} diff --git a/apps/vs-code-designer/src/app/commands/funcCoreTools/installFuncCoreTools.ts b/apps/vs-code-designer/src/app/commands/funcCoreTools/installFuncCoreTools.ts deleted file mode 100644 index 722d41f28c8..00000000000 --- a/apps/vs-code-designer/src/app/commands/funcCoreTools/installFuncCoreTools.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { PackageManager, Platform, autoRuntimeDependenciesPathSettingKey, funcDependencyName, funcPackageName } from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { - downloadAndExtractDependency, - getCpuArchitecture, - getFunctionCoreToolsBinariesReleaseUrl, - getLatestFunctionCoreToolsVersion, -} from '../../utils/binaries'; -import { executeCommand } from '../../utils/funcCoreTools/cpUtils'; -import { getBrewPackageName } from '../../utils/funcCoreTools/getBrewPackageName'; -import { getNpmDistTag } from '../../utils/funcCoreTools/getNpmDistTag'; -import { getGlobalSetting, promptForFuncVersion } from '../../utils/vsCodeConfig/settings'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { FuncVersion, INpmDistTag } from '@microsoft/vscode-extension-logic-apps'; -import { localize } from 'vscode-nls'; - -export async function installFuncCoreToolsBinaries(context: IActionContext, majorVersion?: string): Promise { - ext.outputChannel.show(); - const arch = getCpuArchitecture(); - const targetDirectory = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - context.telemetry.properties.lastStep = 'getLatestFunctionCoreToolsVersion'; - const version = await getLatestFunctionCoreToolsVersion(context, majorVersion); - let azureFunctionCoreToolsReleasesUrl: string; - - context.telemetry.properties.lastStep = 'getFunctionCoreToolsBinariesReleaseUrl'; - switch (process.platform) { - case Platform.windows: { - azureFunctionCoreToolsReleasesUrl = getFunctionCoreToolsBinariesReleaseUrl(version, 'win', arch); - break; - } - - case Platform.linux: { - azureFunctionCoreToolsReleasesUrl = getFunctionCoreToolsBinariesReleaseUrl(version, 'linux', arch); - break; - } - - case Platform.mac: { - azureFunctionCoreToolsReleasesUrl = getFunctionCoreToolsBinariesReleaseUrl(version, 'osx', arch); - break; - } - } - context.telemetry.properties.lastStep = 'downloadAndExtractBinaries'; - await downloadAndExtractDependency(context, azureFunctionCoreToolsReleasesUrl, targetDirectory, funcDependencyName); -} - -export async function installFuncCoreToolsSystem( - context: IActionContext, - packageManagers: PackageManager[], - version?: FuncVersion -): Promise { - version = version || (await promptForFuncVersion(context, localize('selectVersion', 'Select the version of the runtime to install'))); - - ext.outputChannel.show(); - - const distTag: INpmDistTag = await getNpmDistTag(context, version); - const brewPackageName: string = getBrewPackageName(version); - - switch (packageManagers[0]) { - case PackageManager.npm: { - await executeCommand(ext.outputChannel, undefined, 'npm', 'install', '-g', `${funcPackageName}@${distTag.tag}`); - break; - } - case PackageManager.brew: { - await executeCommand(ext.outputChannel, undefined, 'brew', 'tap', 'azure/functions'); - await executeCommand(ext.outputChannel, undefined, 'brew', 'install', brewPackageName); - break; - } - default: - throw new RangeError(localize('invalidPackageManager', 'Invalid package manager "{0}".', packageManagers[0])); - } -} diff --git a/apps/vs-code-designer/src/app/commands/funcCoreTools/uninstallFuncCoreTools.ts b/apps/vs-code-designer/src/app/commands/funcCoreTools/uninstallFuncCoreTools.ts deleted file mode 100644 index 529026ad88f..00000000000 --- a/apps/vs-code-designer/src/app/commands/funcCoreTools/uninstallFuncCoreTools.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { extensionCommand } from '../../../constants'; -import type { PackageManager } from '../../../constants'; -import { commands } from 'vscode'; - -export async function uninstallFuncCoreTools(packageManagers?: PackageManager[]): Promise { - await commands.executeCommand(extensionCommand.azureFunctionsUninstallFuncCoreTools, packageManagers); -} diff --git a/apps/vs-code-designer/src/app/commands/funcCoreTools/updateFuncCoreTools.ts b/apps/vs-code-designer/src/app/commands/funcCoreTools/updateFuncCoreTools.ts deleted file mode 100644 index 35cfe265f22..00000000000 --- a/apps/vs-code-designer/src/app/commands/funcCoreTools/updateFuncCoreTools.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { PackageManager, funcPackageName } from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { localize } from '../../../localize'; -import { executeCommand } from '../../utils/funcCoreTools/cpUtils'; -import { getBrewPackageName, tryGetInstalledBrewPackageName } from '../../utils/funcCoreTools/getBrewPackageName'; -import { getNpmDistTag } from '../../utils/funcCoreTools/getNpmDistTag'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import { nonNullValue } from '@microsoft/vscode-azext-utils'; -import type { FuncVersion, INpmDistTag } from '@microsoft/vscode-extension-logic-apps'; - -export async function updateFuncCoreTools(context: IActionContext, packageManager: PackageManager, version: FuncVersion): Promise { - ext.outputChannel.show(); - const distTag: INpmDistTag = await getNpmDistTag(context, version); - - switch (packageManager) { - case PackageManager.npm: { - await executeCommand(ext.outputChannel, undefined, 'npm', 'install', '-g', `${funcPackageName}@${distTag.tag}`); - break; - } - case PackageManager.brew: { - const brewPackageName: string = getBrewPackageName(version); - const installedBrewPackageName: string = nonNullValue(await tryGetInstalledBrewPackageName(version), 'brewPackageName'); - if (brewPackageName !== installedBrewPackageName) { - await executeCommand(ext.outputChannel, undefined, 'brew', 'uninstall', installedBrewPackageName); - await executeCommand(ext.outputChannel, undefined, 'brew', 'install', brewPackageName); - } else { - await executeCommand(ext.outputChannel, undefined, 'brew', 'upgrade', brewPackageName); - } - break; - } - - default: { - throw new RangeError(localize('invalidPackageManager', 'Invalid package manager "{0}".', packageManager)); - } - } -} diff --git a/apps/vs-code-designer/src/app/commands/funcCoreTools/validateFuncCoreToolsInstalled.ts b/apps/vs-code-designer/src/app/commands/funcCoreTools/validateFuncCoreToolsInstalled.ts deleted file mode 100644 index 9b1ed5c5be2..00000000000 --- a/apps/vs-code-designer/src/app/commands/funcCoreTools/validateFuncCoreToolsInstalled.ts +++ /dev/null @@ -1,123 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { type PackageManager, funcVersionSetting, validateFuncCoreToolsSetting } from '../../../constants'; -import { localize } from '../../../localize'; -import { useBinariesDependencies } from '../../utils/binaries'; -import { executeCommand } from '../../utils/funcCoreTools/cpUtils'; -import { getFunctionsCommand, tryParseFuncVersion } from '../../utils/funcCoreTools/funcVersion'; -import { getFuncPackageManagers } from '../../utils/funcCoreTools/getFuncPackageManagers'; -import { getWorkspaceSetting } from '../../utils/vsCodeConfig/settings'; -import { installFuncCoreToolsBinaries, installFuncCoreToolsSystem } from './installFuncCoreTools'; -import { callWithTelemetryAndErrorHandling, DialogResponses, openUrl } from '@microsoft/vscode-azext-utils'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; -import type { MessageItem } from 'vscode'; - -/** - * Checks if functions core tools is installed, and installs it if needed. - * @param {IActionContext} context - Workflow file path. - * @param {string} message - Message for warning. - * @param {string} fsPath - Workspace file system path. - * @returns {Promise} Returns true if it is installed or was sucessfully installed, otherwise returns false. - */ -export async function validateFuncCoreToolsInstalled(context: IActionContext, message: string, fsPath: string): Promise { - let input: MessageItem | undefined; - let installed = false; - const install: MessageItem = { title: localize('install', 'Install') }; - - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateFuncCoreToolsInstalled', async (innerContext: IActionContext) => { - innerContext.errorHandling.suppressDisplay = true; - - if (!getWorkspaceSetting(validateFuncCoreToolsSetting, fsPath)) { - innerContext.telemetry.properties.validateFuncCoreTools = 'false'; - installed = true; - } else if (await isFuncToolsInstalled()) { - installed = true; - } else if (useBinariesDependencies()) { - installed = await validateFuncCoreToolsInstalledBinaries(innerContext, message, install, input, installed); - } else { - installed = await validateFuncCoreToolsInstalledSystem(innerContext, message, install, input, installed, fsPath); - } - }); - - // validate that Func Tools was installed only if user confirmed - if (input === install && !installed) { - if ( - (await context.ui.showWarningMessage( - localize('failedInstallFuncTools', 'The Azure Functions Core Tools installion has failed and will have to be installed manually.'), - DialogResponses.learnMore - )) === DialogResponses.learnMore - ) { - await openUrl('https://aka.ms/Dqur4e'); - } - } - - return installed; -} - -/** - * Check is functions core tools is installed. - * @returns {Promise} Returns true if installed, otherwise returns false. - */ -async function isFuncToolsInstalled(): Promise { - const funcCommand = getFunctionsCommand(); - try { - await executeCommand(undefined, undefined, funcCommand, '--version'); - return true; - } catch { - return false; - } -} - -async function validateFuncCoreToolsInstalledBinaries( - innerContext: IActionContext, - message: string, - install: MessageItem, - input: MessageItem | undefined, - installed: boolean -): Promise { - const items: MessageItem[] = [install, DialogResponses.learnMore]; - input = await innerContext.ui.showWarningMessage(message, { modal: true }, ...items); - innerContext.telemetry.properties.dialogResult = input.title; - - if (input === install) { - await installFuncCoreToolsBinaries(innerContext); - installed = true; - } else if (input === DialogResponses.learnMore) { - await openUrl('https://aka.ms/Dqur4e'); - } - - return installed; -} - -async function validateFuncCoreToolsInstalledSystem( - innerContext: IActionContext, - message: string, - install: MessageItem, - input: MessageItem | undefined, - installed: boolean, - fsPath: string -): Promise { - const items: MessageItem[] = []; - const packageManagers: PackageManager[] = await getFuncPackageManagers(false /* isFuncInstalled */); - if (packageManagers.length > 0) { - items.push(install); - } else { - items.push(DialogResponses.learnMore); - } - - input = await innerContext.ui.showWarningMessage(message, { modal: true }, ...items); - - innerContext.telemetry.properties.dialogResult = input.title; - - if (input === install) { - const version: FuncVersion | undefined = tryParseFuncVersion(getWorkspaceSetting(funcVersionSetting, fsPath)); - await installFuncCoreToolsSystem(innerContext, packageManagers, version); - installed = true; - } else if (input === DialogResponses.learnMore) { - await openUrl('https://aka.ms/Dqur4e'); - } - return installed; -} diff --git a/apps/vs-code-designer/src/app/commands/funcCoreTools/validateFuncCoreToolsIsLatest.ts b/apps/vs-code-designer/src/app/commands/funcCoreTools/validateFuncCoreToolsIsLatest.ts deleted file mode 100644 index b25f036e12b..00000000000 --- a/apps/vs-code-designer/src/app/commands/funcCoreTools/validateFuncCoreToolsIsLatest.ts +++ /dev/null @@ -1,149 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { PackageManager, funcDependencyName } from '../../../constants'; -import { localize } from '../../../localize'; -import { executeOnFunctions } from '../../functionsExtension/executeOnFunctionsExt'; -import { binariesExist, getLatestFunctionCoreToolsVersion, useBinariesDependencies } from '../../utils/binaries'; -import { startAllDesignTimeApis, stopAllDesignTimeApis } from '../../utils/codeless/startDesignTimeApi'; -import { getFunctionsCommand, getLocalFuncCoreToolsVersion, tryParseFuncVersion } from '../../utils/funcCoreTools/funcVersion'; -import { getBrewPackageName } from '../../utils/funcCoreTools/getBrewPackageName'; -import { getFuncPackageManagers } from '../../utils/funcCoreTools/getFuncPackageManagers'; -import { getNpmDistTag } from '../../utils/funcCoreTools/getNpmDistTag'; -import { sendRequestWithExtTimeout } from '../../utils/requestUtils'; -import { getWorkspaceSetting, updateGlobalSetting } from '../../utils/vsCodeConfig/settings'; -import { installFuncCoreToolsBinaries } from './installFuncCoreTools'; -import { uninstallFuncCoreTools } from './uninstallFuncCoreTools'; -import { updateFuncCoreTools } from './updateFuncCoreTools'; -import { HTTP_METHODS } from '@microsoft/logic-apps-shared'; -import { callWithTelemetryAndErrorHandling, DialogResponses, parseError } from '@microsoft/vscode-azext-utils'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; -import * as semver from 'semver'; -import type { MessageItem } from 'vscode'; - -export async function validateFuncCoreToolsIsLatest(majorVersion?: string): Promise { - if (useBinariesDependencies()) { - await validateFuncCoreToolsIsLatestBinaries(majorVersion); - } else { - await validateFuncCoreToolsIsLatestSystem(); - } -} - -async function validateFuncCoreToolsIsLatestBinaries(majorVersion?: string): Promise { - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateFuncCoreToolsIsLatest', async (context: IActionContext) => { - context.errorHandling.suppressDisplay = true; - context.telemetry.properties.isActivationEvent = 'true'; - - const binaries = binariesExist(funcDependencyName); - context.telemetry.properties.binariesExist = `${binaries}`; - - const localVersion: string | null = binaries ? await getLocalFuncCoreToolsVersion() : null; - context.telemetry.properties.localVersion = localVersion ?? 'null'; - - const newestVersion: string | undefined = binaries ? await getLatestFunctionCoreToolsVersion(context, majorVersion) : undefined; - const isOutdated = binaries && localVersion && newestVersion && semver.gt(newestVersion, localVersion); - - const shouldInstall = !binaries || localVersion === null || isOutdated; - - if (shouldInstall) { - if (isOutdated) { - context.telemetry.properties.outOfDateFunc = 'true'; - stopAllDesignTimeApis(); - } - - await installFuncCoreToolsBinaries(context, majorVersion); - await startAllDesignTimeApis(); - } - - context.telemetry.properties.binaryCommand = getFunctionsCommand(); - }); -} - -async function validateFuncCoreToolsIsLatestSystem(): Promise { - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateFuncCoreToolsIsLatest', async (context: IActionContext) => { - context.errorHandling.suppressDisplay = true; - context.telemetry.properties.isActivationEvent = 'true'; - - const showMultiCoreToolsWarningKey = 'showMultiCoreToolsWarning'; - const showMultiCoreToolsWarning = !!getWorkspaceSetting(showMultiCoreToolsWarningKey); - - if (showMultiCoreToolsWarning) { - const packageManagers: PackageManager[] = await getFuncPackageManagers(true /* isFuncInstalled */); - let packageManager: PackageManager; - - if (packageManagers.length === 0) { - return; - } - if (packageManagers.length === 1) { - packageManager = packageManagers[0]; - context.telemetry.properties.packageManager = packageManager; - } else { - context.telemetry.properties.multiFunc = 'true'; - - if (showMultiCoreToolsWarning) { - const message: string = localize('multipleInstalls', 'Detected multiple installs of the func cli.'); - const selectUninstall: MessageItem = { title: localize('selectUninstall', 'Select version to uninstall') }; - const result: MessageItem = await context.ui.showWarningMessage(message, selectUninstall, DialogResponses.dontWarnAgain); - - if (result === selectUninstall) { - await executeOnFunctions(uninstallFuncCoreTools, context, packageManagers); - } else if (result === DialogResponses.dontWarnAgain) { - await updateGlobalSetting(showMultiCoreToolsWarningKey, false); - } - } - - return; - } - const localVersion: string | null = await getLocalFuncCoreToolsVersion(); - if (!localVersion) { - return; - } - context.telemetry.properties.localVersion = localVersion; - - const versionFromSetting: FuncVersion | undefined = tryParseFuncVersion(localVersion); - if (versionFromSetting === undefined) { - return; - } - - const newestVersion: string | undefined = await getNewestFunctionRuntimeVersion(packageManager, versionFromSetting, context); - if (!newestVersion) { - return; - } - - if (semver.major(newestVersion) === semver.major(localVersion) && semver.gt(newestVersion, localVersion)) { - context.telemetry.properties.outOfDateFunc = 'true'; - stopAllDesignTimeApis(); - await updateFuncCoreTools(context, packageManager, versionFromSetting); - await startAllDesignTimeApis(); - } - } - }); -} - -async function getNewestFunctionRuntimeVersion( - packageManager: PackageManager | undefined, - versionFromSetting: FuncVersion, - context: IActionContext -): Promise { - try { - if (packageManager === PackageManager.brew) { - const packageName: string = getBrewPackageName(versionFromSetting); - const brewRegistryUri = `https://raw.githubusercontent.com/Azure/homebrew-functions/master/Formula/${packageName}.rb`; - const response = await sendRequestWithExtTimeout(context, { url: brewRegistryUri, method: HTTP_METHODS.GET }); - const brewInfo: string = response.bodyAsText; - const matches: RegExpMatchArray | null = brewInfo.match(/version\s+["']([^"']+)["']/i); - - if (matches && matches.length > 1) { - return matches[1]; - } - } else { - return (await getNpmDistTag(context, versionFromSetting)).value; - } - } catch (error) { - context.telemetry.properties.latestRuntimeError = parseError(error).message; - } - - return undefined; -} diff --git a/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScripts.ts b/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScripts.ts index 6d4bcd278b9..6b7d3770e3e 100644 --- a/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScripts.ts +++ b/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScripts.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { + EXTENSION_BUNDLE_VERSION, localSettingsFileName, workflowLocationKey, workflowResourceGroupNameKey, @@ -93,7 +94,7 @@ export async function generateDeploymentScripts(context: IActionContext, node?: : 'false'; context.telemetry.properties.currentWorkflowBundleVersion = ext.currentBundleVersion.has(projectPath) ? ext.currentBundleVersion.get(projectPath) - : ext.defaultBundleVersion; + : EXTENSION_BUNDLE_VERSION; if (error instanceof UserCancelledError) { context.telemetry.properties.result = 'Canceled'; diff --git a/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScriptsSteps/adoDeploymentScriptsSteps/GenerateADODeploymentScriptsStep.ts b/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScriptsSteps/adoDeploymentScriptsSteps/GenerateADODeploymentScriptsStep.ts index f400e0215b8..5ec613cd61d 100644 --- a/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScriptsSteps/adoDeploymentScriptsSteps/GenerateADODeploymentScriptsStep.ts +++ b/apps/vs-code-designer/src/app/commands/generateDeploymentScripts/generateDeploymentScriptsSteps/adoDeploymentScriptsSteps/GenerateADODeploymentScriptsStep.ts @@ -17,11 +17,12 @@ import { ext } from '../../../../../extensionVariables'; import { localize } from '../../../../../localize'; import { parameterizeConnections } from '../../../parameterizeConnections'; import { FileManagement } from '../../iacGestureHelperFunctions'; -import { deploymentDirectory, managementApiPrefix, workflowFileName } from '../../../../../constants'; +import { deploymentDirectory, EXTENSION_BUNDLE_VERSION, managementApiPrefix, workflowFileName } from '../../../../../constants'; import { unzipLogicAppArtifacts } from '../../../../utils/taskUtils'; import { startDesignTimeApi } from '../../../../utils/codeless/startDesignTimeApi'; import { getAuthorizationToken, getCloudHost } from '../../../../utils/codeless/getAuthorizationToken'; import type { IAzureDeploymentScriptsContext } from '../../generateDeploymentScripts'; +import { getPublicUrl } from '../../../../utils/extension'; export class GenerateADODeploymentScriptsStep extends AzureWizardExecuteStep { public priority = 250; @@ -151,7 +152,7 @@ export class GenerateADODeploymentScriptsStep extends AzureWizardExecuteStep(show64BitWarningSetting)) { - const message: string = localize( - '64BitWarning', - 'In order to debug .NET Framework functions in VS Code, you must install a 64-bit version of the Azure Functions Core Tools.' - ); - - try { - const result: MessageItem = await context.ui.showWarningMessage( - message, - DialogResponses.learnMore, - DialogResponses.dontWarnAgain - ); - - if (result === DialogResponses.learnMore) { - await openUrl('https://aka.ms/azFunc64bit'); - } else if (result === DialogResponses.dontWarnAgain) { - await updateGlobalSetting(show64BitWarningSetting, false); - } - } catch (err) { - // swallow cancellations (aka if they clicked the 'x' button to dismiss the warning) and proceed to create project - if (!parseError(err).isUserCancelledError) { - throw err; - } - } - } - } - const targetFramework: string = await getTargetFramework(projFile); await this.setDeploySubpath(context, path.posix.join('bin', 'Release', targetFramework, 'publish')); this.debugSubpath = getDotnetDebugSubpath(targetFramework); @@ -108,28 +69,17 @@ export class InitDotnetProjectStep extends InitProjectStepBase { protected getTasks(): TaskDefinition[] { const commonArgs: string[] = ['/property:GenerateFullPaths=true', '/consoleloggerparameters:NoSummary']; const releaseArgs: string[] = ['--configuration', 'Release']; - const funcBinariesExist = binariesExist(funcDependencyName); - const binariesOptions = funcBinariesExist - ? { - options: { - cwd: this.debugSubpath, - env: { - PATH: '${config:azureLogicAppsStandard.autoRuntimeDependenciesPath}\\NodeJs;${config:azureLogicAppsStandard.autoRuntimeDependenciesPath}\\DotNetSDK;$env:PATH', - }, - }, - } - : {}; return [ { label: 'clean', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['clean', ...commonArgs], type: 'process', problemMatcher: '$msCompile', }, { label: 'build', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['build', ...commonArgs], type: 'process', dependsOn: 'clean', @@ -141,14 +91,14 @@ export class InitDotnetProjectStep extends InitProjectStepBase { }, { label: 'clean release', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['clean', ...releaseArgs, ...commonArgs], type: 'process', problemMatcher: '$msCompile', }, { label: dotnetPublishTaskLabel, - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['publish', ...releaseArgs, ...commonArgs], type: 'process', dependsOn: 'clean release', @@ -156,11 +106,10 @@ export class InitDotnetProjectStep extends InitProjectStepBase { }, { label: 'func: host start', - type: funcBinariesExist ? 'shell' : func, + type: 'shell', dependsOn: 'build', - ...binariesOptions, - command: funcBinariesExist ? '${config:azureLogicAppsStandard.funcCoreToolsBinaryPath}' : hostStartCommand, - args: funcBinariesExist ? ['host', 'start'] : undefined, + command: 'func', + args: ['host', 'start'], isBackground: true, problemMatcher: funcWatchProblemMatcher, }, diff --git a/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initProjectStep.ts b/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initProjectStep.ts index 1805ac02d39..951f7eb471d 100644 --- a/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initProjectStep.ts +++ b/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initProjectStep.ts @@ -2,37 +2,25 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { binariesExist } from '../../utils/binaries'; -import { extensionCommand, func, funcDependencyName, funcWatchProblemMatcher, hostStartCommand } from '../../../constants'; +import { extensionCommand, funcWatchProblemMatcher } from '../../../constants'; import { InitScriptProjectStep } from './initScriptProjectStep'; import type { ITaskInputs, ISettingToAdd } from '@microsoft/vscode-extension-logic-apps'; import type { TaskDefinition } from 'vscode'; export class InitProjectStep extends InitScriptProjectStep { protected getTasks(): TaskDefinition[] { - const funcBinariesExist = binariesExist(funcDependencyName); - const binariesOptions = funcBinariesExist - ? { - options: { - env: { - PATH: '${config:azureLogicAppsStandard.autoRuntimeDependenciesPath}\\NodeJs;${config:azureLogicAppsStandard.autoRuntimeDependenciesPath}\\DotNetSDK;$env:PATH', - }, - }, - } - : {}; return [ { label: 'generateDebugSymbols', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['${input:getDebugSymbolDll}'], type: 'process', problemMatcher: '$msCompile', }, { - type: funcBinariesExist ? 'shell' : func, - command: funcBinariesExist ? '${config:azureLogicAppsStandard.funcCoreToolsBinaryPath}' : hostStartCommand, - args: funcBinariesExist ? ['host', 'start'] : undefined, - ...binariesOptions, + type: 'shell', + command: 'func', + args: ['host', 'start'], problemMatcher: funcWatchProblemMatcher, isBackground: true, label: 'func: host start', diff --git a/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initScriptProjectStep.ts b/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initScriptProjectStep.ts index 2d958275bfb..53975143f77 100644 --- a/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initScriptProjectStep.ts +++ b/apps/vs-code-designer/src/app/commands/initProjectForVSCode/initScriptProjectStep.ts @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { extInstallTaskName, func, funcDependencyName, funcWatchProblemMatcher, hostStartCommand } from '../../../constants'; -import { binariesExist } from '../../utils/binaries'; -import { getLocalFuncCoreToolsVersion } from '../../utils/funcCoreTools/funcVersion'; +import { extInstallTaskName, funcWatchProblemMatcher } from '../../../constants'; import { InitProjectStepBase } from './initProjectStepBase'; import type { IProjectWizardContext } from '@microsoft/vscode-extension-logic-apps'; -import { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; import * as fse from 'fs-extra'; import * as path from 'path'; -import * as semver from 'semver'; import type { TaskDefinition } from 'vscode'; /** @@ -26,11 +22,6 @@ export class InitScriptProjectStep extends InitProjectStepBase { if (await fse.pathExists(extensionsCsprojPath)) { this.useFuncExtensionsInstall = true; context.telemetry.properties.hasExtensionsCsproj = 'true'; - } else if (context.version === FuncVersion.v2) { - // no need to check v1 or v3+ - const currentVersion: string | null = await getLocalFuncCoreToolsVersion(); - // Starting after this version, projects can use extension bundle instead of running "func extensions install" - this.useFuncExtensionsInstall = !!currentVersion && semver.lte(currentVersion, '2.5.553'); } } catch { // use default of false @@ -50,23 +41,12 @@ export class InitScriptProjectStep extends InitProjectStepBase { } protected getTasks(): TaskDefinition[] { - const funcBinariesExist = binariesExist(funcDependencyName); - const binariesOptions = funcBinariesExist - ? { - options: { - env: { - PATH: '${config:azureLogicAppsStandard.autoRuntimeDependenciesPath}\\NodeJs;${config:azureLogicAppsStandard.autoRuntimeDependenciesPath}\\DotNetSDK;$env:PATH', - }, - }, - } - : {}; return [ { label: 'func: host start', - type: funcBinariesExist ? 'shell' : func, - command: funcBinariesExist ? '${config:azureLogicAppsStandard.funcCoreToolsBinaryPath}' : hostStartCommand, - args: funcBinariesExist ? ['host', 'start'] : undefined, - ...binariesOptions, + type: 'shell', + command: 'func', + args: ['host', 'start'], problemMatcher: funcWatchProblemMatcher, dependsOn: this.useFuncExtensionsInstall ? extInstallTaskName : undefined, isBackground: true, diff --git a/apps/vs-code-designer/src/app/commands/nodeJs/installNodeJs.ts b/apps/vs-code-designer/src/app/commands/nodeJs/installNodeJs.ts deleted file mode 100644 index 2002276747f..00000000000 --- a/apps/vs-code-designer/src/app/commands/nodeJs/installNodeJs.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*------------------p--------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { Platform, autoRuntimeDependenciesPathSettingKey, nodeJsDependencyName } from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { - downloadAndExtractDependency, - getCpuArchitecture, - getLatestNodeJsVersion, - getNodeJsBinariesReleaseUrl, -} from '../../utils/binaries'; -import { getGlobalSetting } from '../../utils/vsCodeConfig/settings'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; - -export async function installNodeJs(context: IActionContext, majorVersion?: string): Promise { - ext.outputChannel.show(); - const arch = getCpuArchitecture(); - const targetDirectory = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - context.telemetry.properties.lastStep = 'getLatestNodeJsVersion'; - const version = await getLatestNodeJsVersion(context, majorVersion); - let nodeJsReleaseUrl: string; - - context.telemetry.properties.lastStep = 'getNodeJsBinariesReleaseUrl'; - switch (process.platform) { - case Platform.windows: { - nodeJsReleaseUrl = getNodeJsBinariesReleaseUrl(version, 'win', arch); - break; - } - - case Platform.linux: { - nodeJsReleaseUrl = getNodeJsBinariesReleaseUrl(version, 'linux', arch); - break; - } - - case Platform.mac: { - nodeJsReleaseUrl = getNodeJsBinariesReleaseUrl(version, 'darwin', arch); - break; - } - } - - context.telemetry.properties.lastStep = 'downloadAndExtractBinaries'; - await downloadAndExtractDependency(context, nodeJsReleaseUrl, targetDirectory, nodeJsDependencyName); -} diff --git a/apps/vs-code-designer/src/app/commands/nodeJs/validateNodeJsInstalled.ts b/apps/vs-code-designer/src/app/commands/nodeJs/validateNodeJsInstalled.ts deleted file mode 100644 index 7a092c0fc9b..00000000000 --- a/apps/vs-code-designer/src/app/commands/nodeJs/validateNodeJsInstalled.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { validateNodeJsSetting } from '../../../constants'; -import { localize } from '../../../localize'; -import { executeCommand } from '../../utils/funcCoreTools/cpUtils'; -import { getNodeJsCommand } from '../../utils/nodeJs/nodeJsVersion'; -import { getWorkspaceSetting } from '../../utils/vsCodeConfig/settings'; -import { installNodeJs } from './installNodeJs'; -import { callWithTelemetryAndErrorHandling, DialogResponses, openUrl } from '@microsoft/vscode-azext-utils'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { MessageItem } from 'vscode'; - -/** - * Checks if node is installed, and installs it if needed. - * TODO(aeldridge): Unused - * @param {IActionContext} context - Workflow file path. - * @param {string} message - Message for warning. - * @param {string} fsPath - Workspace file system path. - * @returns {Promise} Returns true if it is installed or was sucessfully installed, otherwise returns false. - */ -export async function validateNodeJsInstalled(context: IActionContext, message: string, fsPath: string): Promise { - let input: MessageItem | undefined; - let installed = false; - const install: MessageItem = { title: localize('install', 'Install') }; - - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateNodeJsIsInstalled', async (innerContext: IActionContext) => { - innerContext.errorHandling.suppressDisplay = true; - - if (!getWorkspaceSetting(validateNodeJsSetting, fsPath)) { - innerContext.telemetry.properties.validateDotNet = 'false'; - installed = true; - } else if (await isNodeJsInstalled()) { - installed = true; - } else { - const items: MessageItem[] = [install, DialogResponses.learnMore]; - input = await innerContext.ui.showWarningMessage(message, { modal: true }, ...items); - innerContext.telemetry.properties.dialogResult = input.title; - - if (input === install) { - await installNodeJs(innerContext); - installed = true; - } else if (input === DialogResponses.learnMore) { - await openUrl('https://nodejs.org/en/download'); - } - } - }); - - // validate that DotNet was installed only if user confirmed - if (input === install && !installed) { - if ( - (await context.ui.showWarningMessage( - localize('failedInstallDotNet', 'The Node JS installation failed. Please manually install instead.'), - DialogResponses.learnMore - )) === DialogResponses.learnMore - ) { - await openUrl('https://nodejs.org/en/download'); - } - } - - return installed; -} - -/** - * Check is dotnet is installed. - * @returns {Promise} Returns true if installed, otherwise returns false. - */ -export async function isNodeJsInstalled(): Promise { - try { - await executeCommand(undefined, undefined, getNodeJsCommand(), '--version'); - return true; - } catch { - return false; - } -} diff --git a/apps/vs-code-designer/src/app/commands/nodeJs/validateNodeJsIsLatest.ts b/apps/vs-code-designer/src/app/commands/nodeJs/validateNodeJsIsLatest.ts deleted file mode 100644 index 853a313418c..00000000000 --- a/apps/vs-code-designer/src/app/commands/nodeJs/validateNodeJsIsLatest.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { nodeJsDependencyName } from '../../../constants'; -import { localize } from '../../../localize'; -import { binariesExist, getLatestNodeJsVersion } from '../../utils/binaries'; -import { getLocalNodeJsVersion, getNodeJsCommand } from '../../utils/nodeJs/nodeJsVersion'; -import { getWorkspaceSetting, updateGlobalSetting } from '../../utils/vsCodeConfig/settings'; -import { installNodeJs } from './installNodeJs'; -import { callWithTelemetryAndErrorHandling, DialogResponses, openUrl } from '@microsoft/vscode-azext-utils'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import * as semver from 'semver'; -import type { MessageItem } from 'vscode'; - -export async function validateNodeJsIsLatest(majorVersion?: string): Promise { - await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.validateNodeJsIsLatest', async (context: IActionContext) => { - context.errorHandling.suppressDisplay = true; - context.telemetry.properties.isActivationEvent = 'true'; - const showNodeJsWarningKey = 'showNodeJsWarning'; - const showNodeJsWarning = !!getWorkspaceSetting(showNodeJsWarningKey); - const binaries = binariesExist(nodeJsDependencyName); - context.telemetry.properties.binariesExist = `${binaries}`; - - if (!binaries) { - await installNodeJs(context, majorVersion); - context.telemetry.properties.binaryCommand = `${getNodeJsCommand()}`; - } else if (showNodeJsWarning) { - context.telemetry.properties.binaryCommand = `${getNodeJsCommand()}`; - const localVersion: string | null = await getLocalNodeJsVersion(context); - context.telemetry.properties.localVersion = localVersion; - const newestVersion: string | undefined = await getLatestNodeJsVersion(context, majorVersion); - - if (localVersion === null) { - await installNodeJs(context, majorVersion); - } else if (semver.major(newestVersion) === semver.major(localVersion) && semver.gt(newestVersion, localVersion)) { - context.telemetry.properties.outOfDateDotNet = 'true'; - const message: string = localize( - 'outdatedNodeJsRuntime', - 'Update your local Node JS 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://nodejs.org/en/download'); - } else if (result === update) { - await installNodeJs(context, majorVersion); - } else if (result === DialogResponses.dontWarnAgain) { - await updateGlobalSetting(showNodeJsWarningKey, false); - } - } while (result === DialogResponses.learnMore); - } - } - }); -} diff --git a/apps/vs-code-designer/src/app/commands/registerCommands.ts b/apps/vs-code-designer/src/app/commands/registerCommands.ts index 1cf382f02ce..9971acbd0fe 100644 --- a/apps/vs-code-designer/src/app/commands/registerCommands.ts +++ b/apps/vs-code-designer/src/app/commands/registerCommands.ts @@ -2,7 +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 { extensionCommand } from '../../constants'; +import { EXTENSION_BUNDLE_VERSION, extensionCommand } from '../../constants'; import { ext } from '../../extensionVariables'; import { executeOnFunctions } from '../functionsExtension/executeOnFunctionsExt'; import { LogicAppResourceTree } from '../tree/LogicAppResourceTree'; @@ -11,8 +11,6 @@ import { editAppSetting } from './appSettings/editAppSetting'; import { renameAppSetting } from './appSettings/renameAppSetting'; import { toggleSlotSetting } from './appSettings/toggleSlotSetting'; import { uploadAppSettings } from './appSettings/uploadAppSettings'; -import { disableValidateAndInstallBinaries, resetValidateAndInstallBinaries } from './binaries/resetValidateAndInstallBinaries'; -import { validateAndInstallBinaries } from './binaries/validateAndInstallBinaries'; import { browseWebsite } from './browseWebsite'; import { buildCustomCodeFunctionsProject } from './buildCustomCodeFunctionsProject'; import { configureDeploymentSource } from './configureDeploymentSource'; @@ -155,9 +153,6 @@ export function registerCommands(): void { registerCommandWithTreeNodeUnwrapping(extensionCommand.configureDeploymentSource, configureDeploymentSource); registerCommandWithTreeNodeUnwrapping(extensionCommand.startRemoteDebug, startRemoteDebug); registerCommand(extensionCommand.parameterizeConnections, parameterizeConnections); - registerCommandWithTreeNodeUnwrapping(extensionCommand.validateAndInstallBinaries, validateAndInstallBinaries); - registerCommandWithTreeNodeUnwrapping(extensionCommand.resetValidateAndInstallBinaries, resetValidateAndInstallBinaries); - registerCommandWithTreeNodeUnwrapping(extensionCommand.disableValidateAndInstallBinaries, disableValidateAndInstallBinaries); // Data Mapper Commands registerCommand(extensionCommand.createNewDataMap, (context: IActionContext) => createNewDataMapCmd(context)); registerCommand(extensionCommand.loadDataMapFile, (context: IActionContext, uri: Uri) => loadDataMapFileCmd(context, uri)); @@ -194,7 +189,7 @@ export function registerCommands(): void { errorContext.telemetry.properties.handlingData = JSON.stringify({ message: errorData.message, extensionVersion: ext.extensionVersion, - bundleVersion: ext.latestBundleVersion, + bundleVersion: EXTENSION_BUNDLE_VERSION, correlationId: correlationId, }); }); diff --git a/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerBase.ts b/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerBase.ts index 71861ddd8fb..15a4b319601 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerBase.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerBase.ts @@ -27,6 +27,7 @@ export abstract class OpenDesignerBase { protected apiVersion: string; protected panelGroupKey: string; protected baseUrl: string; + protected webviewBaseUrl: string; protected workflowRuntimeBaseUrl: string; protected connectionData: ConnectionsData; protected panel: WebviewPanel; diff --git a/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerForLocalProject.ts b/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerForLocalProject.ts index 90207140254..dc91745fd38 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerForLocalProject.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/openDesigner/openDesignerForLocalProject.ts @@ -1,4 +1,4 @@ -import { localSettingsFileName, managementApiPrefix, workflowAppApiVersion } from '../../../../constants'; +import { EXTENSION_BUNDLE_VERSION, localSettingsFileName, managementApiPrefix, workflowAppApiVersion } from '../../../../constants'; import { ext } from '../../../../extensionVariables'; import { localize } from '../../../../localize'; import { getLocalSettingsJson } from '../../../utils/appSettings/localSettings'; @@ -45,7 +45,7 @@ import { env, ProgressLocation, Uri, ViewColumn, window, workspace } from 'vscod import type { WebviewPanel, ProgressOptions } from 'vscode'; import { saveBlankUnitTest } from '../unitTest/saveBlankUnitTest'; import { createHttpHeaders } from '@azure/core-rest-pipeline'; -import { getBundleVersionNumber } from '../../../utils/bundleFeed'; +import { getPublicUrl } from '../../../utils/extension'; export default class OpenDesignerForLocalProject extends OpenDesignerBase { private readonly workflowFilePath: string; @@ -118,17 +118,7 @@ export default class OpenDesignerForLocalProject extends OpenDesignerBase { throw new Error(localize('designTimePortNotFound', 'Design time port not found.')); } - // if (!ext.runtimeInstances.has(this.projectPath)) { - // throw new Error(localize('runtimeNotRunning', `Runtime is not running for project ${this.projectPath}.`)); - // } - // const runtimePort = ext.runtimeInstances.get(this.projectPath).port; - // if (!runtimePort) { - // throw new Error(localize('runtimePortNotFound', 'Runtime port not found.')); - // } - - this.baseUrl = `http://localhost:${designTimePort}${managementApiPrefix}`; - // this.workflowRuntimeBaseUrl = `http://localhost:${runtimePort}${managementApiPrefix}`; - + this.baseUrl = `http://localhost:${designTimePort}/${managementApiPrefix}`; this.panel = window.createWebviewPanel( this.panelGroupKey, // Key used to reference the panel this.panelName, // Title display in the tab @@ -400,7 +390,8 @@ export default class OpenDesignerForLocalProject extends OpenDesignerBase { if (!designTimePort) { throw new Error(localize('designTimePortNotFound', 'Design time port not found.')); } - const url = `http://localhost:${designTimePort}${managementApiPrefix}/workflows/${this.workflowName}/validatePartial?api-version=${this.apiVersion}`; + const publicUrl = await getPublicUrl(`http://localhost:${designTimePort}`); + const url = `${publicUrl}${managementApiPrefix}/workflows/${this.workflowName}/validatePartial?api-version=${this.apiVersion}`; try { const headers = createHttpHeaders({ 'Content-Type': 'application/json', @@ -533,7 +524,6 @@ export default class OpenDesignerForLocalProject extends OpenDesignerBase { const customCodeData: Record = await getCustomCodeFromFiles(this.workflowFilePath); const workflowDetails = await getManualWorkflowsInLocalProject(projectPath, this.workflowName); const artifacts = await getArtifactsInLocalProject(projectPath); - const bundleVersionNumber = await getBundleVersionNumber(); let localSettings: Record; let azureDetails: AzureConnectorDetails; @@ -561,7 +551,7 @@ export default class OpenDesignerForLocalProject extends OpenDesignerBase { artifacts, schemaArtifacts: this.schemaArtifacts, mapArtifacts: this.mapArtifacts, - extensionBundleVersion: bundleVersionNumber, + extensionBundleVersion: EXTENSION_BUNDLE_VERSION, }; } diff --git a/apps/vs-code-designer/src/app/commands/workflows/openMonitoringView/openMonitoringViewForLocal.ts b/apps/vs-code-designer/src/app/commands/workflows/openMonitoringView/openMonitoringViewForLocal.ts index ae10b51ebb7..8f1ba83722d 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/openMonitoringView/openMonitoringViewForLocal.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/openMonitoringView/openMonitoringViewForLocal.ts @@ -2,7 +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 { localSettingsFileName, managementApiPrefix } from '../../../../constants'; +import { EXTENSION_BUNDLE_VERSION, localSettingsFileName, managementApiPrefix } from '../../../../constants'; import { ext } from '../../../../extensionVariables'; import { localize } from '../../../../localize'; import { getLocalSettingsJson } from '../../../utils/appSettings/localSettings'; @@ -32,7 +32,7 @@ import type { WebviewPanel } from 'vscode'; import { Uri, ViewColumn } from 'vscode'; import { getArtifactsInLocalProject } from '../../../utils/codeless/artifacts'; import { saveBlankUnitTest } from '../unitTest/saveBlankUnitTest'; -import { getBundleVersionNumber } from '../../../utils/bundleFeed'; +import { getPublicUrl } from '../../../utils/extension'; export default class OpenMonitoringViewForLocal extends OpenMonitoringViewBase { private projectPath: string | undefined; @@ -75,7 +75,8 @@ export default class OpenMonitoringViewForLocal extends OpenMonitoringViewBase { this.projectPath = await getLogicAppProjectRoot(this.context, this.workflowFilePath); const connectionsData = await getConnectionsFromFile(this.context, this.workflowFilePath); const parametersData = await getParametersFromFile(this.context, this.workflowFilePath); - this.baseUrl = `http://localhost:${ext.workflowRuntimePort}${managementApiPrefix}`; + const publicUrl = await getPublicUrl(`http://localhost:${ext.workflowRuntimePort}`); + this.baseUrl = `${publicUrl}${managementApiPrefix}`; if (this.projectPath) { this.localSettings = (await getLocalSettingsJson(this.context, path.join(this.projectPath, localSettingsFileName))).Values; @@ -192,7 +193,6 @@ export default class OpenMonitoringViewForLocal extends OpenMonitoringViewBase { const workflowContent: any = JSON.parse(readFileSync(this.workflowFilePath, 'utf8')); const parametersData: Record = await getParametersFromFile(this.context, this.workflowFilePath); const customCodeData: Record = await getCustomCodeFromFiles(this.workflowFilePath); - const bundleVersionNumber = await getBundleVersionNumber(); let localSettings: Record; let azureDetails: AzureConnectorDetails; @@ -219,7 +219,7 @@ export default class OpenMonitoringViewForLocal extends OpenMonitoringViewBase { standardApp: getStandardAppData(this.workflowName, { ...workflowContent, definition: {} }), schemaArtifacts: this.schemaArtifacts, mapArtifacts: this.mapArtifacts, - extensionBundleVersion: bundleVersionNumber, + extensionBundleVersion: EXTENSION_BUNDLE_VERSION, }; } } diff --git a/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts b/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts index 79cfd2a9511..a44c9487370 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts @@ -32,6 +32,7 @@ import { readFileSync } from 'fs'; import { basename, dirname, join } from 'path'; import * as path from 'path'; import * as vscode from 'vscode'; +import { getPublicUrl } from '../../utils/extension'; export async function openOverview(context: IAzureConnectorsContext, node: vscode.Uri | RemoteWorkflowTreeItem | undefined): Promise { let workflowFilePath: string; @@ -56,11 +57,22 @@ export async function openOverview(context: IAzureConnectorsContext, node: vscod workflowName = basename(dirname(workflowFilePath)); panelName = `${vscode.workspace.name}-${workflowName}-overview`; workflowContent = JSON.parse(readFileSync(workflowFilePath, 'utf8')); - baseUrl = `http://localhost:${ext.workflowRuntimePort}${managementApiPrefix}`; + const publicUrl = await getPublicUrl(`http://localhost:${ext.workflowRuntimePort}`); + baseUrl = `${publicUrl}${managementApiPrefix}`; + apiVersion = '2019-10-01-edge-preview'; isLocal = true; triggerName = getTriggerName(workflowContent.definition); - callbackInfo = await getLocalWorkflowCallbackInfo(context, workflowContent.definition, baseUrl, workflowName, triggerName, apiVersion); + // Get callback info. Function will internalize rebasing to public origin. + callbackInfo = await getLocalWorkflowCallbackInfo( + context, + workflowContent.definition, + `http://localhost:${ext.workflowRuntimePort}/${managementApiPrefix}`, + workflowName, + triggerName, + apiVersion, + publicUrl + ); const projectPath = await getLogicAppProjectRoot(context, workflowFilePath); localSettings = projectPath ? (await getLocalSettingsJson(context, join(projectPath, localSettingsFileName))).Values || {} : {}; @@ -193,7 +205,8 @@ async function getLocalWorkflowCallbackInfo( baseUrl: string, workflowName: string, triggerName: string, - apiVersion: string + apiVersion: string, + publicOrigin?: string ): Promise { const requestTriggerName = getRequestTriggerName(definition); if (requestTriggerName) { @@ -203,17 +216,54 @@ async function getLocalWorkflowCallbackInfo( url, method: HTTP_METHODS.POST, }); - return JSON.parse(response); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { + const callbackInfo: ICallbackUrlResponse = JSON.parse(response); + return rebaseCallbackInfoOrigins(callbackInfo, baseUrl, publicOrigin); + } catch { return undefined; } } else { - return { + const fallback: ICallbackUrlResponse = { value: `${baseUrl}/workflows/${workflowName}/triggers/${triggerName}/run?api-version=${apiVersion}`, method: HTTP_METHODS.POST, }; + return rebaseCallbackInfoOrigins(fallback, baseUrl, publicOrigin); + } +} + +function rebaseCallbackInfoOrigins(callbackInfo: ICallbackUrlResponse, localBaseUrl: string, publicOrigin?: string): ICallbackUrlResponse { + if (!publicOrigin) { + return callbackInfo; + } + try { + const localUrlObj = new URL(localBaseUrl); + const localOrigin = `${localUrlObj.protocol}//${localUrlObj.host}`; + // Normalize public origin to avoid trailing slash duplication (e.g. http://127.0.0.1:PORT/) + const normalizedPublicOrigin = publicOrigin.replace(/\/+$/, ''); + const swap = (raw?: string) => { + if (!raw) { + return raw; + } + if (!raw.startsWith(localOrigin)) { + return raw; + } + const rest = raw.slice(localOrigin.length); // begins with '/' + return `${normalizedPublicOrigin}${rest}`; // normalizedPublicOrigin has no trailing '/' + }; + callbackInfo.value = swap(callbackInfo.value)!; + if (callbackInfo.basePath) { + callbackInfo.basePath = swap(callbackInfo.basePath); + } else if (callbackInfo.value) { + try { + const vUrl = new URL(callbackInfo.value); + callbackInfo.basePath = `${vUrl.origin}${vUrl.pathname}`; + } catch { + // ignore + } + } + } catch { + // ignore } + return callbackInfo; } function getWorkflowStateType(workflowName: string, kind: string, settings: Record): string { diff --git a/apps/vs-code-designer/src/app/commands/workflows/switchToDotnetProject.ts b/apps/vs-code-designer/src/app/commands/workflows/switchToDotnetProject.ts index 122a0ad99cc..617c7284b8a 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/switchToDotnetProject.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/switchToDotnetProject.ts @@ -16,7 +16,6 @@ import { import { localize } from '../../../localize'; import { initProjectForVSCode } from '../../commands/initProjectForVSCode/initProjectForVSCode'; import { DotnetTemplateProvider } from '../../templates/dotnet/DotnetTemplateProvider'; -import { useBinariesDependencies } from '../../utils/binaries'; import { getDotnetBuildFile, addNugetPackagesToBuildFile, @@ -29,7 +28,7 @@ import { allowLocalSettingsToPublishDirectory, addNugetPackagesToBuildFileByName, } from '../../utils/codeless/updateBuildFile'; -import { getLocalDotNetVersionFromBinaries, getProjFiles, getTemplateKeyFromProjFile } from '../../utils/dotnet/dotnet'; +import { getProjFiles, getTemplateKeyFromProjFile } from '../../utils/dotnet/dotnet'; import { getFramework, executeDotnetTemplateCommand } from '../../utils/dotnet/executeDotnetTemplateCommand'; import { wrapArgInQuotes } from '../../utils/funcCoreTools/cpUtils'; import { tryGetMajorVersion, tryParseFuncVersion } from '../../utils/funcCoreTools/funcVersion'; @@ -43,7 +42,6 @@ import { FuncVersion, ProjectLanguage } from '@microsoft/vscode-extension-logic- import * as fse from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; -import { validateDotNetIsInstalled } from '../dotnet/validateDotNetInstalled'; import { tryGetLogicAppProjectRoot } from '../../utils/verifyIsProject'; import { ext } from '../../../extensionVariables'; @@ -51,23 +49,13 @@ export async function switchToDotnetProjectCommand(context: IProjectWizardContex switchToDotnetProject(context, target); } -export async function switchToDotnetProject( - context: IProjectWizardContext, - target: vscode.Uri, - localDotNetMajorVersion = '8', - isCodeful = false -) { +export async function switchToDotnetProject(context: IProjectWizardContext, target: vscode.Uri, isCodeful = false) { if (target === undefined || Object.keys(target).length === 0) { const workspaceFolder = await getWorkspaceFolder(context); const projectPath = await tryGetLogicAppProjectRoot(context, workspaceFolder); target = vscode.Uri.file(projectPath); } - const isDotNetInstalled = await validateDotNetIsInstalled(context, target.fsPath); - if (!isDotNetInstalled) { - return; - } - let version: FuncVersion | undefined = tryParseFuncVersion(getWorkspaceSetting(funcVersionSetting, target.fsPath)); if (isCodeful) { version = FuncVersion.v4; @@ -134,8 +122,6 @@ export async function switchToDotnetProject( const projectPath: string = target.fsPath; const projTemplateKey = await getTemplateKeyFromProjFile(context, projectPath, version, ProjectLanguage.CSharp); const dotnetVersion = await getFramework(context, projectPath, isCodeful); - const useBinaries = useBinariesDependencies(); - const dotnetLocalVersion = useBinaries ? await getLocalDotNetVersionFromBinaries(localDotNetMajorVersion) : ''; await deleteBundleProjectFiles(target); await renameBundleProjectFiles(target); @@ -156,9 +142,7 @@ export async function switchToDotnetProject( await copyBundleProjectFiles(target); await updateBuildFile(context, target, dotnetVersion, isCodeful); - if (useBinaries) { - await createGlobalJsonFile(dotnetLocalVersion, target.fsPath); - } + await createGlobalJsonFile(dotnetVersion, target.fsPath); const workspaceFolder: vscode.WorkspaceFolder | undefined = getContainingWorkspace(target.fsPath); diff --git a/apps/vs-code-designer/src/app/commands/workflows/unitTest/createUnitTest.ts b/apps/vs-code-designer/src/app/commands/workflows/unitTest/createUnitTest.ts index 0e99fb90e82..e415f2b2b6d 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/unitTest/createUnitTest.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/unitTest/createUnitTest.ts @@ -33,6 +33,7 @@ import axios from 'axios'; import { ext } from '../../../../extensionVariables'; import { unzipLogicAppArtifacts } from '../../../utils/taskUtils'; import { syncCloudSettings } from '../../syncCloudSettings'; +import { getPublicUrl } from '../../../utils/extension'; /** * Handles the creation of a unit test for a Logic App workflow. @@ -168,7 +169,7 @@ async function generateUnitTestFromRun( } logTelemetry(context, { runtimePort: ext.workflowRuntimePort.toString() }); - const baseUrl = `http://localhost:${ext.workflowRuntimePort}`; + const baseUrl = await getPublicUrl(`http://localhost:${ext.workflowRuntimePort}`); const apiUrl = `${baseUrl}/runtime/webhooks/workflow/api/management/workflows/${encodeURIComponent(workflowName)}/runs/${encodeURIComponent(runId)}/generateUnitTest`; ext.outputChannel.appendLog(localize('apiUrl', `Calling API URL: ${apiUrl}`)); diff --git a/apps/vs-code-designer/src/app/commands/workflows/unitTest/runUnitTest.ts b/apps/vs-code-designer/src/app/commands/workflows/unitTest/runUnitTest.ts index 74a07bc1ead..45285068a63 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/unitTest/runUnitTest.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/unitTest/runUnitTest.ts @@ -2,7 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { defaultExtensionBundlePathValue, runUnitTestEvent, testResultsDirectoryName } from '../../../../constants'; +import { + defaultExtensionBundlePathValue, + EXTENSION_BUNDLE_VERSION, + runUnitTestEvent, + testResultsDirectoryName, +} from '../../../../constants'; import { ext } from '../../../../extensionVariables'; import { localize } from '../../../../localize'; import { getLatestUnitTest, getTestsDirectory, getUnitTestName, pickUnitTest } from '../../../utils/unitTests'; @@ -11,7 +16,6 @@ import * as vscode from 'vscode'; import * as cp from 'child_process'; import * as path from 'path'; import { getWorkspacePath, isMultiRootWorkspace } from '../../../utils/workspace'; -import { getLatestBundleVersion } from '../../../utils/bundleFeed'; import { activateAzurite } from '../../../utils/azurite/activateAzurite'; import { TestFile } from '../../../tree/unitTestTree/testFile'; import type { UnitTestExecutionResult } from '@microsoft/vscode-extension-logic-apps'; @@ -104,10 +108,9 @@ export async function runUnitTestFromPath(context: IActionContext, unitTestPath: const logicAppName = path.relative(testDirectory, unitTestPath).split(path.sep)[0]; const workflowName = path.basename(path.dirname(unitTestPath)); const unitTestName = getUnitTestName(path.basename(unitTestPath)); - const bundleVersionNumber = await getLatestBundleVersion(defaultExtensionBundlePathValue); const pathToExe = path.join( defaultExtensionBundlePathValue, - bundleVersionNumber, + EXTENSION_BUNDLE_VERSION, 'UnitTestExecutor', 'Microsoft.Azure.Workflows.UnitTestExecutor.exe' ); diff --git a/apps/vs-code-designer/src/app/debug/validatePreDebug.ts b/apps/vs-code-designer/src/app/debug/validatePreDebug.ts index 343c84c5bde..322f2599a86 100644 --- a/apps/vs-code-designer/src/app/debug/validatePreDebug.ts +++ b/apps/vs-code-designer/src/app/debug/validatePreDebug.ts @@ -11,7 +11,6 @@ import { Platform, } from '../../constants'; import { localize } from '../../localize'; -import { validateFuncCoreToolsInstalled } from '../commands/funcCoreTools/validateFuncCoreToolsInstalled'; import { getAzureWebJobsStorage, setLocalAppSetting } from '../utils/appSettings/localSettings'; import { getDebugConfigs, isDebugConfigEqual } from '../utils/vsCodeConfig/launch'; import { getWorkspaceSetting, getFunctionsWorkerRuntime } from '../utils/vsCodeConfig/settings'; @@ -32,22 +31,15 @@ export async function preDebugValidate(context: IActionContext, projectPath: str try { context.telemetry.properties.lastValidateStep = 'funcInstalled'; - const message: string = localize( - 'installFuncTools', - 'You must have the Azure Functions Core Tools installed to debug your local functions.' - ); - shouldContinue = await validateFuncCoreToolsInstalled(context, message, projectPath); - if (shouldContinue) { - const projectLanguage: string | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath); - context.telemetry.properties.projectLanguage = projectLanguage; + const projectLanguage: string | undefined = getWorkspaceSetting(projectLanguageSetting, projectPath); + context.telemetry.properties.projectLanguage = projectLanguage; - context.telemetry.properties.lastValidateStep = 'workerRuntime'; - await validateWorkerRuntime(context, projectLanguage, projectPath); + context.telemetry.properties.lastValidateStep = 'workerRuntime'; + await validateWorkerRuntime(context, projectLanguage, projectPath); - context.telemetry.properties.lastValidateStep = 'emulatorRunning'; - shouldContinue = await validateEmulatorIsRunning(context, projectPath); - } + context.telemetry.properties.lastValidateStep = 'emulatorRunning'; + shouldContinue = await validateEmulatorIsRunning(context, projectPath); } catch (error) { if (parseError(error).isUserCancelledError) { shouldContinue = false; diff --git a/apps/vs-code-designer/src/app/funcConfig/host.ts b/apps/vs-code-designer/src/app/funcConfig/host.ts index 974da5b2fa4..5310c14202f 100644 --- a/apps/vs-code-designer/src/app/funcConfig/host.ts +++ b/apps/vs-code-designer/src/app/funcConfig/host.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { defaultRoutePrefix } from '../../constants'; import { isObject, isNullOrUndefined } from '@microsoft/logic-apps-shared'; -import type { IBundleMetadata, IHostJsonV1, IHostJsonV2, IParsedHostJson } from '@microsoft/vscode-extension-logic-apps'; -import { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; +import type { IBundleMetadata, IHostJsonV2, IParsedHostJson } from '@microsoft/vscode-extension-logic-apps'; class ParsedHostJsonV2 implements IParsedHostJson { public data: IHostJsonV2; @@ -30,25 +29,6 @@ class ParsedHostJsonV2 implements IParsedHostJson { } } -class ParsedHostJsonV1 implements IParsedHostJson { - public data: IHostJsonV1; - - public constructor(data: unknown) { - if (!isNullOrUndefined(data) && isObject(data)) { - this.data = data as IHostJsonV1; - } else { - this.data = {}; - } - } - - public get routePrefix(): string { - if (this.data.http && this.data.http.routePrefix !== undefined) { - return this.data.http.routePrefix; - } - return defaultRoutePrefix; - } -} - -export function parseHostJson(data: unknown, version: FuncVersion | undefined): IParsedHostJson { - return version === FuncVersion.v1 ? new ParsedHostJsonV1(data) : new ParsedHostJsonV2(data); +export function parseHostJson(data: unknown): IParsedHostJson { + return new ParsedHostJsonV2(data); } diff --git a/apps/vs-code-designer/src/app/templates/TemplateProviderBase.ts b/apps/vs-code-designer/src/app/templates/TemplateProviderBase.ts index 8ee9e4fe445..b4a70e6b349 100644 --- a/apps/vs-code-designer/src/app/templates/TemplateProviderBase.ts +++ b/apps/vs-code-designer/src/app/templates/TemplateProviderBase.ts @@ -3,19 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ext } from '../../extensionVariables'; -import { localize } from '../../localize'; import { NotImplementedError } from '../utils/errors'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { ITemplates } from '@microsoft/vscode-extension-logic-apps'; -import { FuncVersion, TemplateType } from '@microsoft/vscode-extension-logic-apps'; +import type { ITemplates, FuncVersion } from '@microsoft/vscode-extension-logic-apps'; +import { TemplateType } from '@microsoft/vscode-extension-logic-apps'; import * as path from 'path'; import * as vscode from 'vscode'; import { Disposable } from 'vscode'; -const v3BackupTemplatesVersion = '3.4.1'; -const v2BackupTemplatesVersion = '2.47.1'; -const v1BackupTemplatesVersion = '1.11.0'; - export abstract class TemplateProviderBase implements Disposable { protected static templateVersionCacheKey = 'templateVersion'; protected static projTemplateKeyCacheKey = 'projectTemplateKey'; @@ -102,19 +97,6 @@ export abstract class TemplateProviderBase implements Disposable { await this.updateCachedValue(TemplateProviderBase.projTemplateKeyCacheKey, this._sessionProjKey); } - public getBackupTemplateVersion(): string { - switch (this.version) { - case FuncVersion.v1: - return v1BackupTemplatesVersion; - case FuncVersion.v2: - return v2BackupTemplatesVersion; - case FuncVersion.v3: - return v3BackupTemplatesVersion; - default: - throw new RangeError(localize('invalidVersion', 'Invalid version "{0}".', this.version)); - } - } - protected async getCacheKeySuffix(): Promise { return ''; } @@ -126,10 +108,6 @@ export abstract class TemplateProviderBase implements Disposable { private async getCacheKey(key: string): Promise { key = key + (await this.getCacheKeySuffix()); - if (this.version !== FuncVersion.v1) { - key = `${key}.${this.version}`; - } - if (this.templateType !== TemplateType.Script) { key = `${key}.${this.templateType}`; } diff --git a/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts b/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts index 3dc3a68c755..dd4ef78009b 100644 --- a/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts +++ b/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts @@ -204,8 +204,7 @@ export class LogicAppResourceTree implements ResolvedAppResourceBase { } catch { // ignore and use default } - const version: FuncVersion = await this.getVersion(context); - result = parseHostJson(data, version); + result = parseHostJson(data); this._cachedHostJson = result; } diff --git a/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts b/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts index 8982352a4cc..e7763589963 100644 --- a/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts @@ -105,7 +105,7 @@ export class RemoteWorkflowTreeItem extends AzExtTreeItem { const requestTriggerName = getRequestTriggerName(node.workflowFileContent.definition); if (requestTriggerName) { try { - const url = `${this.parent.parent.id}/hostruntime${managementApiPrefix}/workflows/${this.name}/triggers/${triggerName}/listCallbackUrl?api-version=${workflowAppApiVersion}`; + const url = `${this.parent.parent.id}/hostruntime/${managementApiPrefix}/workflows/${this.name}/triggers/${triggerName}/listCallbackUrl?api-version=${workflowAppApiVersion}`; const response = await sendAzureRequest(url, this.parent._context, HTTP_METHODS.POST, node.subscription); return response.parsedBody; } catch { diff --git a/apps/vs-code-designer/src/app/tree/subscriptionTree/subscriptionTreeItem.ts b/apps/vs-code-designer/src/app/tree/subscriptionTree/subscriptionTreeItem.ts index 5bbbc2c6dd0..9aa45c8d600 100644 --- a/apps/vs-code-designer/src/app/tree/subscriptionTree/subscriptionTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/subscriptionTree/subscriptionTreeItem.ts @@ -61,8 +61,12 @@ import { } from '@microsoft/vscode-azext-azureutils'; import type { AzExtTreeItem, AzureWizardExecuteStep, AzureWizardPromptStep, IActionContext } from '@microsoft/vscode-azext-utils'; import { nonNullProp, parseError, AzureWizard } from '@microsoft/vscode-azext-utils'; -import type { ILogicAppWizardContext, ICreateLogicAppContext, IIdentityWizardContext } from '@microsoft/vscode-extension-logic-apps'; -import { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; +import type { + ILogicAppWizardContext, + ICreateLogicAppContext, + IIdentityWizardContext, + FuncVersion, +} from '@microsoft/vscode-extension-logic-apps'; export class SubscriptionTreeItem extends SubscriptionTreeItemBase { public readonly childTypeLabel: string = localize('LogicApp', 'Logic App (Standard) in Azure'); @@ -124,11 +128,6 @@ export class SubscriptionTreeItem extends SubscriptionTreeItemBase { ...(await createActivityContext()), }); - if (version === FuncVersion.v1) { - // v1 doesn't support linux - wizardContext.newSiteOS = WebsiteOS.windows; - } - await setRegionsTask(wizardContext); const promptSteps: AzureWizardPromptStep[] = []; diff --git a/apps/vs-code-designer/src/app/utils/__test__/binaries.test.ts b/apps/vs-code-designer/src/app/utils/__test__/binaries.test.ts deleted file mode 100644 index e3b086ead7d..00000000000 --- a/apps/vs-code-designer/src/app/utils/__test__/binaries.test.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest'; -import * as fs from 'fs'; -import axios from 'axios'; -import * as vscode from 'vscode'; -import { - downloadAndExtractDependency, - binariesExist, - getLatestDotNetVersion, - getLatestFunctionCoreToolsVersion, - getLatestNodeJsVersion, - getNodeJsBinariesReleaseUrl, - getFunctionCoreToolsBinariesReleaseUrl, - getDotNetBinariesReleaseUrl, - getCpuArchitecture, - getDependencyTimeout, - installBinaries, - useBinariesDependencies, -} from '../binaries'; -import { ext } from '../../../extensionVariables'; -import { DependencyVersion, Platform } from '../../../constants'; -import { executeCommand } from '../funcCoreTools/cpUtils'; -import { getNpmCommand } from '../nodeJs/nodeJsVersion'; -import { getGlobalSetting, getWorkspaceSetting } from '../vsCodeConfig/settings'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import { isNodeJsInstalled } from '../../commands/nodeJs/validateNodeJsInstalled'; - -vi.mock('../funcCoreTools/cpUtils'); -vi.mock('../nodeJs/nodeJsVersion'); -vi.mock('../../../onboarding'); -vi.mock('../vsCodeConfig/settings'); -vi.mock('../../commands/nodeJs/validateNodeJsInstalled'); - -describe('binaries', () => { - describe('downloadAndExtractDependency', () => { - let context: IActionContext; - - beforeEach(() => { - context = { - telemetry: { - properties: {}, - }, - } as IActionContext; - }); - - it('should download and extract dependency', async () => { - const downloadUrl = 'https://example.com/dependency.zip'; - const targetFolder = 'targetFolder'; - const dependencyName = 'dependency'; - const folderName = 'folderName'; - const dotNetVersion = '6.0'; - - const writer = { - on: vi.fn(), - } as any; - - (axios.get as Mock).mockResolvedValue({ - data: { - pipe: vi.fn().mockImplementation((writer) => { - writer.on('finish'); - }), - }, - }); - - (fs.createWriteStream as Mock).mockReturnValue(writer); - - await downloadAndExtractDependency(context, downloadUrl, targetFolder, dependencyName, folderName, dotNetVersion); - - expect(fs.mkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true }); - expect(fs.chmodSync).toHaveBeenCalledWith(expect.any(String), 0o777); - expect(executeCommand).toHaveBeenCalledWith(ext.outputChannel, undefined, 'echo', `Downloading dependency from: ${downloadUrl}`); - }); - - it('should throw error when the compression file extension is not supported', async () => { - const downloadUrl = 'https://example.com/dependency.zip222'; - const targetFolder = 'targetFolder'; - const dependencyName = 'dependency'; - const folderName = 'folderName'; - const dotNetVersion = '6.0'; - - await expect( - downloadAndExtractDependency(context, downloadUrl, targetFolder, dependencyName, folderName, dotNetVersion) - ).rejects.toThrowError(); - }); - }); - - describe('binariesExist', () => { - beforeEach(() => { - (getGlobalSetting as Mock).mockReturnValue('binariesLocation'); - }); - it('should return true if binaries exist', () => { - (fs.existsSync as Mock).mockReturnValue(true); - - const result = binariesExist('dependencyName'); - - expect(result).toBe(true); - }); - - it('should return false if binaries do not exist', () => { - (fs.existsSync as Mock).mockReturnValue(false); - - const result = binariesExist('dependencyName'); - - expect(result).toBe(false); - }); - - it('should return false if useBinariesDependencies returns false', () => { - (fs.existsSync as Mock).mockReturnValue(false); - (getGlobalSetting as Mock).mockReturnValue(false); - const result = binariesExist('dependencyName'); - - expect(result).toBe(false); - }); - }); - - describe('getLatestDotNetVersion', () => { - let context: IActionContext; - let majorVersion: string; - - beforeEach(() => { - context = { - telemetry: { - properties: {}, - }, - } as IActionContext; - majorVersion = '6'; - }); - - it('should return the latest .NET version', async () => { - const response = [{ tag_name: 'v6.0.0' }]; - - (axios.get as Mock).mockResolvedValue({ data: response, status: 200 }); - - const result = await getLatestDotNetVersion(context, majorVersion); - - expect(result).toBe('6.0.0'); - }); - - it('should throw error when api call to get dotnet version fails and return fallback version', async () => { - const showErrorMessage = vi.fn(); - (axios.get as Mock).mockResolvedValue({ data: [], status: 500 }); - - vscode.window.showErrorMessage = showErrorMessage; - - const result = await getLatestDotNetVersion(context, majorVersion); - expect(result).toBe(DependencyVersion.dotnet6); - expect(showErrorMessage).toHaveBeenCalled(); - }); - - it('should return fallback dotnet version when no major version is sent', async () => { - const result = await getLatestDotNetVersion(context); - - expect(result).toBe(DependencyVersion.dotnet6); - }); - }); - - describe('getLatestFunctionCoreToolsVersion', () => { - let context: IActionContext; - let majorVersion: string; - - beforeEach(() => { - context = { - telemetry: { - properties: {}, - }, - } as IActionContext; - majorVersion = '3'; - }); - - it('should return the latest Function Core Tools version from npm', async () => { - const npmVersion = '3.0.0'; - (isNodeJsInstalled as Mock).mockResolvedValue(true); - (getNpmCommand as Mock).mockReturnValue('npm'); - (executeCommand as Mock).mockResolvedValue(npmVersion); - - const result = await getLatestFunctionCoreToolsVersion(context, majorVersion); - - expect(result).toBe(npmVersion); - expect(context.telemetry.properties.latestVersionSource).toBe('node'); - }); - - it('should return the latest Function Core Tools version from GitHub', async () => { - const githubVersion = '3.0.0'; - (isNodeJsInstalled as Mock).mockResolvedValue(false); - (axios.get as Mock).mockResolvedValue({ data: { tag_name: `v${githubVersion}` }, status: 200 }); - - const result = await getLatestFunctionCoreToolsVersion(context, majorVersion); - - expect(result).toBe(githubVersion); - expect(context.telemetry.properties.latestVersionSource).toBe('github'); - }); - - it('should return the fallback Function Core Tools version', async () => { - const showErrorMessage = vi.fn(); - (isNodeJsInstalled as Mock).mockResolvedValue(false); - (axios.get as Mock).mockResolvedValue({ data: [], status: 500 }); - - vscode.window.showErrorMessage = showErrorMessage; - - const result = await getLatestFunctionCoreToolsVersion(context, majorVersion); - - expect(result).toBe(DependencyVersion.funcCoreTools); - expect(showErrorMessage).toHaveBeenCalled(); - expect(context.telemetry.properties.latestVersionSource).toBe('fallback'); - }); - - it('should return the fallback Function Core Tools version when no major version is sent', async () => { - (isNodeJsInstalled as Mock).mockResolvedValue(false); - const result = await getLatestFunctionCoreToolsVersion(context); - - expect(result).toBe(DependencyVersion.funcCoreTools); - expect(context.telemetry.properties.latestVersionSource).toBe('fallback'); - }); - }); - - describe('getLatestNodeJsVersion', () => { - let context: IActionContext; - let majorVersion: string; - - beforeEach(() => { - context = { - telemetry: { - properties: {}, - }, - } as IActionContext; - majorVersion = '14'; - }); - - it('should return the latest Node.js version', async () => { - const response = [{ tag_name: 'v14.0.0' }]; - (axios.get as any).mockResolvedValue({ data: response, status: 200 }); - const result = await getLatestNodeJsVersion(context, majorVersion); - - expect(result).toBe('14.0.0'); - }); - - it('should throw error when api call to get dotnet version fails', async () => { - const showErrorMessage = vi.fn(); - (axios.get as Mock).mockResolvedValue({ data: [], status: 500 }); - - vscode.window.showErrorMessage = showErrorMessage; - - const result = await getLatestNodeJsVersion(context, majorVersion); - expect(result).toBe(DependencyVersion.nodeJs); - expect(showErrorMessage).toHaveBeenCalled(); - }); - - it('should return fallback nodejs version when no major version is sent', async () => { - const result = await getLatestNodeJsVersion(context); - expect(result).toBe(DependencyVersion.nodeJs); - }); - }); - - describe('getNodeJsBinariesReleaseUrl', () => { - const version = '14.0.0'; - const arch = 'x64'; - - it('should return the correct Node.js binaries release URL for windows', () => { - const osPlatform = 'win'; - - const result = getNodeJsBinariesReleaseUrl(version, osPlatform, arch); - console.log(result); - - expect(result).toStrictEqual('https://nodejs.org/dist/v14.0.0/node-v14.0.0-win-x64.zip'); - }); - it('should return the correct Node.js binaries release URL for non windows', () => { - const osPlatform = 'darwin'; - const result = getNodeJsBinariesReleaseUrl(version, osPlatform, arch); - - expect(result).toStrictEqual('https://nodejs.org/dist/v14.0.0/node-v14.0.0-darwin-x64.tar.gz'); - }); - }); - - describe('getFunctionCoreToolsBinariesReleaseUrl', () => { - it('should return the correct Function Core Tools binaries release URL', () => { - const version = '3.0.0'; - const osPlatform = 'win-x64'; - const arch = 'x64'; - const result = getFunctionCoreToolsBinariesReleaseUrl(version, osPlatform, arch); - - expect(result).toStrictEqual( - `https://github.com/Azure/azure-functions-core-tools/releases/download/${version}/Azure.Functions.Cli.${osPlatform}-${arch}.${version}.zip` - ); - }); - }); - - describe('getDotNetBinariesReleaseUrl', () => { - const originalPlatform = process.platform; - - afterEach(() => { - vi.restoreAllMocks(); - Object.defineProperty(process, 'platform', { - value: originalPlatform, - }); - }); - - it('should return the correct .NET binaries release URL for windows', () => { - vi.stubGlobal('process', { - ...process, - platform: Platform.windows, - }); - const result = getDotNetBinariesReleaseUrl(); - - expect(result).toBe('https://dot.net/v1/dotnet-install.ps1'); - }); - - it('should return the correct .NET binaries release URL for non windows', () => { - vi.stubGlobal('process', { - ...process, - platform: Platform.mac, - }); - const result = getDotNetBinariesReleaseUrl(); - - expect(result).toBe('https://dot.net/v1/dotnet-install.sh'); - }); - }); - - describe('getCpuArchitecture', () => { - const originalArch = process.arch; - - afterEach(() => { - vi.restoreAllMocks(); - Object.defineProperty(process, 'arch', { - value: originalArch, - }); - }); - - it('should return the correct CPU architecture', () => { - vi.stubGlobal('process', { - ...process, - arch: 'x64', - }); - const result = getCpuArchitecture(); - - expect(result).toBe('x64'); - }); - - it('should throw an error for unsupported CPU architecture', () => { - (process as any).arch = vi.stubGlobal('process', { - ...process, - arch: 'unsupported', - }); - expect(() => getCpuArchitecture()).toThrowError('Unsupported CPU architecture: unsupported'); - }); - }); - - describe('getDependencyTimeout', () => { - it('should return the dependency timeout value', () => { - (getWorkspaceSetting as Mock).mockReturnValue(60); - - const result = getDependencyTimeout(); - - expect(result).toBe(60); - }); - - it('should throw an error for invalid timeout value', () => { - (getWorkspaceSetting as Mock).mockReturnValue('invalid'); - - expect(() => getDependencyTimeout()).toThrowError('The setting "invalid" must be a number, but instead found "invalid".'); - }); - }); - - describe('installBinaries', () => { - let context: IActionContext; - - beforeEach(() => { - context = { - telemetry: { - properties: {}, - }, - } as IActionContext; - }); - it('should install binaries', async () => { - (getGlobalSetting as Mock).mockReturnValue(true); - - await installBinaries(context); - - expect(context.telemetry.properties.autoRuntimeDependenciesValidationAndInstallationSetting).toBe('true'); - }); - - it('should not install binaries', async () => { - (getGlobalSetting as Mock).mockReturnValue(false); - - await installBinaries(context); - - expect(context.telemetry.properties.autoRuntimeDependenciesValidationAndInstallationSetting).toBe('false'); - }); - }); - - describe('useBinariesDependencies', () => { - it('should return true if binaries dependencies are used', () => { - (getGlobalSetting as Mock).mockReturnValue(true); - - const result = useBinariesDependencies(); - - expect(result).toBe(true); - }); - - it('should return false if binaries dependencies are not used', () => { - (getGlobalSetting as Mock).mockReturnValue(false); - - const result = useBinariesDependencies(); - - expect(result).toBe(false); - }); - }); -}); diff --git a/apps/vs-code-designer/src/app/utils/__test__/bundleFeed.test.ts b/apps/vs-code-designer/src/app/utils/__test__/bundleFeed.test.ts index 64781abcc82..6050a7809bb 100644 --- a/apps/vs-code-designer/src/app/utils/__test__/bundleFeed.test.ts +++ b/apps/vs-code-designer/src/app/utils/__test__/bundleFeed.test.ts @@ -1,4 +1,4 @@ -import { getBundleVersionNumber, getExtensionBundleFolder } from '../bundleFeed'; +import { getExtensionBundleFolder } from '../bundleFeed'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import * as fse from 'fs-extra'; import * as path from 'path'; @@ -275,107 +275,3 @@ describe('getExtensionBundleFolder', () => { }); }); }); - -describe('getBundleVersionNumber', () => { - const mockBundleFolderRoot = 'C:\\mock\\bundle\\root\\ExtensionBundles\\'; - const mockBundleFolder = path.join(mockBundleFolderRoot, extensionBundleId); - - beforeEach(() => { - vi.clearAllMocks(); - // Mock getExtensionBundleFolder to return a proper Windows path - const mockCommandOutput = `C:\\mock\\bundle\\root\\ExtensionBundles\\${extensionBundleId}\\1.0.0\n`; - mockedExecuteCommand.mockResolvedValue(mockCommandOutput); - }); - - it('should return the highest version number from available bundle folders', async () => { - const mockFolders = ['1.0.0', '2.1.0', '1.5.0', 'some-file.txt']; - mockedFse.readdir.mockResolvedValue(mockFolders as any); - mockedFse.stat.mockImplementation((filePath: any) => { - const fileName = path.basename(filePath.toString()); - return Promise.resolve({ - isDirectory: () => fileName !== 'some-file.txt', - } as any); - }); - - const result = await getBundleVersionNumber(); - - expect(result).toBe('2.1.0'); - expect(mockedFse.readdir).toHaveBeenCalledWith(mockBundleFolder); - }); - - it('should handle version numbers with different digit counts', async () => { - const mockFolders = ['1.0.0', '10.2.1', '2.15.3']; - mockedFse.readdir.mockResolvedValue(mockFolders as any); - mockedFse.stat.mockImplementation(() => { - return Promise.resolve({ - isDirectory: () => true, - } as any); - }); - - const result = await getBundleVersionNumber(); - - expect(result).toBe('10.2.1'); - }); - - it('should return default version when only non-directory files exist', async () => { - const mockFolders = ['file1.txt', 'file2.log']; - mockedFse.readdir.mockResolvedValue(mockFolders as any); - mockedFse.stat.mockImplementation(() => { - return Promise.resolve({ - isDirectory: () => false, - } as any); - }); - - const result = await getBundleVersionNumber(); - - expect(result).toBe('0.0.0'); - }); - - it('should handle single version folder', async () => { - const mockFolders = ['1.2.3']; - mockedFse.readdir.mockResolvedValue(mockFolders as any); - mockedFse.stat.mockImplementation(() => { - return Promise.resolve({ - isDirectory: () => true, - } as any); - }); - - const result = await getBundleVersionNumber(); - - expect(result).toBe('1.2.3'); - }); - - it('should throw error when no bundle folders found', async () => { - mockedFse.readdir.mockResolvedValue([] as any); - - await expect(getBundleVersionNumber()).rejects.toThrow('Extension bundle could not be found.'); - }); - - it('should handle mixed version formats correctly', async () => { - const mockFolders = ['1.0', '1.0.0', '1.0.0.1']; - mockedFse.readdir.mockResolvedValue(mockFolders as any); - mockedFse.stat.mockImplementation(() => { - return Promise.resolve({ - isDirectory: () => true, - } as any); - }); - - const result = await getBundleVersionNumber(); - - expect(result).toBe('1.0.0.1'); - }); - - it('should call executeCommand to get the bundle root path', async () => { - const mockFolders = ['1.0.0']; - mockedFse.readdir.mockResolvedValue(mockFolders as any); - mockedFse.stat.mockImplementation(() => { - return Promise.resolve({ - isDirectory: () => true, - } as any); - }); - - await getBundleVersionNumber(); - - expect(mockedExecuteCommand).toHaveBeenCalledWith(expect.anything(), '/mock/workspace', 'func', 'GetExtensionBundlePath'); - }); -}); diff --git a/apps/vs-code-designer/src/app/utils/__test__/debug.test.ts b/apps/vs-code-designer/src/app/utils/__test__/debug.test.ts index 4f4cef0d31f..28b4ba34301 100644 --- a/apps/vs-code-designer/src/app/utils/__test__/debug.test.ts +++ b/apps/vs-code-designer/src/app/utils/__test__/debug.test.ts @@ -21,81 +21,9 @@ describe('debug', () => { isCodeless: true, }); }); - - it('should return launch configuration for .NET Framework custom code with v1 function runtime', () => { - const result = getDebugConfiguration(FuncVersion.v1, 'TestLogicApp', TargetFramework.NetFx); - - expect(result).toEqual({ - name: 'Run/Debug logic app with local function TestLogicApp', - type: 'logicapp', - request: 'launch', - funcRuntime: 'clr', - customCodeRuntime: 'clr', - isCodeless: true, - }); - }); - - it('should return launch configuration for .NET Framework custom code with v3 function runtime', () => { - const result = getDebugConfiguration(FuncVersion.v3, 'TestLogicApp', TargetFramework.NetFx); - - expect(result).toEqual({ - name: 'Run/Debug logic app with local function TestLogicApp', - type: 'logicapp', - request: 'launch', - funcRuntime: 'coreclr', - customCodeRuntime: 'clr', - isCodeless: true, - }); - }); - - it('should return launch configuration for .NET 8 custom code with v2 function runtime', () => { - const result = getDebugConfiguration(FuncVersion.v2, 'MyApp', TargetFramework.Net8); - - expect(result).toEqual({ - name: 'Run/Debug logic app with local function MyApp', - type: 'logicapp', - request: 'launch', - funcRuntime: 'coreclr', - customCodeRuntime: 'coreclr', - isCodeless: true, - }); - }); }); describe('without custom code target framework', () => { - it('should return attach configuration for v1 function runtime', () => { - const result = getDebugConfiguration(FuncVersion.v1, 'TestLogicApp'); - - expect(result).toEqual({ - name: 'Run/Debug logic app TestLogicApp', - type: 'clr', - request: 'attach', - processId: `\${command:${extensionCommand.pickProcess}}`, - }); - }); - - it('should return attach configuration for v2 function runtime', () => { - const result = getDebugConfiguration(FuncVersion.v2, 'TestLogicApp'); - - expect(result).toEqual({ - name: 'Run/Debug logic app TestLogicApp', - type: 'coreclr', - request: 'attach', - processId: `\${command:${extensionCommand.pickProcess}}`, - }); - }); - - it('should return attach configuration for v3 function runtime', () => { - const result = getDebugConfiguration(FuncVersion.v3, 'TestLogicApp'); - - expect(result).toEqual({ - name: 'Run/Debug logic app TestLogicApp', - type: 'coreclr', - request: 'attach', - processId: `\${command:${extensionCommand.pickProcess}}`, - }); - }); - it('should return attach configuration for v4 function runtime', () => { const result = getDebugConfiguration(FuncVersion.v4, 'MyLogicApp'); @@ -122,17 +50,6 @@ describe('debug', () => { }); }); - it('should handle empty logic app name without custom code', () => { - const result = getDebugConfiguration(FuncVersion.v3, ''); - - expect(result).toEqual({ - name: 'Run/Debug logic app ', - type: 'coreclr', - request: 'attach', - processId: `\${command:${extensionCommand.pickProcess}}`, - }); - }); - it('should handle special characters in logic app name', () => { const logicAppName = 'Test-App_With.Special@Characters'; const result = getDebugConfiguration(FuncVersion.v4, logicAppName); diff --git a/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts b/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts new file mode 100644 index 00000000000..dc150e538a6 --- /dev/null +++ b/apps/vs-code-designer/src/app/utils/__test__/extension.test.ts @@ -0,0 +1,253 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { getPublicUrl, getExtensionVersion } from '../extension'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import * as vscode from 'vscode'; + +describe('extension utils', () => { + describe('getPublicUrl', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should convert a local URL to a public external URL', async () => { + const localUrl = 'http://localhost:3000'; + const expectedExternalUrl = 'https://external-url.example.com:3000'; + + const mockParsedUri = { + scheme: 'http', + authority: 'localhost:3000', + toString: () => localUrl, + } as vscode.Uri; + + const mockExternalUri = { + scheme: 'https', + authority: 'external-url.example.com:3000', + toString: () => expectedExternalUrl, + } as vscode.Uri; + + const parseSpy = vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + const asExternalUriSpy = vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(localUrl); + + expect(parseSpy).toHaveBeenCalledWith(localUrl); + expect(asExternalUriSpy).toHaveBeenCalledWith(mockParsedUri); + expect(result).toBe(expectedExternalUrl); + }); + + it('should handle URLs with different ports', async () => { + const localUrl = 'http://localhost:8080'; + const expectedExternalUrl = 'https://external-url.example.com:8080'; + + const mockParsedUri = { + scheme: 'http', + authority: 'localhost:8080', + toString: () => localUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => expectedExternalUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(localUrl); + + expect(result).toBe(expectedExternalUrl); + }); + + it('should handle HTTPS URLs', async () => { + const localUrl = 'https://localhost:5001'; + const expectedExternalUrl = 'https://external-url.example.com:5001'; + + const mockParsedUri = { + scheme: 'https', + authority: 'localhost:5001', + toString: () => localUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => expectedExternalUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(localUrl); + + expect(result).toBe(expectedExternalUrl); + }); + + it('should handle URLs with paths', async () => { + const localUrl = 'http://localhost:3000/api/callback'; + const expectedExternalUrl = 'https://external-url.example.com:3000/api/callback'; + + const mockParsedUri = { + scheme: 'http', + authority: 'localhost:3000', + path: '/api/callback', + toString: () => localUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => expectedExternalUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(localUrl); + + expect(result).toBe(expectedExternalUrl); + }); + + it('should handle URLs with query parameters', async () => { + const localUrl = 'http://localhost:3000/callback?code=123&state=abc'; + const expectedExternalUrl = 'https://external-url.example.com:3000/callback?code=123&state=abc'; + + const mockParsedUri = { + scheme: 'http', + authority: 'localhost:3000', + path: '/callback', + query: 'code=123&state=abc', + toString: () => localUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => expectedExternalUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(localUrl); + + expect(result).toBe(expectedExternalUrl); + }); + + it('should handle non-localhost URLs', async () => { + const localUrl = 'http://127.0.0.1:3000'; + const expectedExternalUrl = 'https://external-url.example.com:3000'; + + const mockParsedUri = { + scheme: 'http', + authority: '127.0.0.1:3000', + toString: () => localUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => expectedExternalUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(localUrl); + + expect(result).toBe(expectedExternalUrl); + }); + + it('should return the same URL if no external mapping is needed', async () => { + const publicUrl = 'https://example.com:3000'; + + const mockParsedUri = { + scheme: 'https', + authority: 'example.com:3000', + toString: () => publicUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => publicUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(publicUrl); + + expect(result).toBe(publicUrl); + }); + + it('should handle vscode-webview:// scheme URLs', async () => { + const webviewUrl = 'vscode-webview://some-webview-id'; + const expectedExternalUrl = 'vscode-webview://some-webview-id'; + + const mockParsedUri = { + scheme: 'vscode-webview', + authority: 'some-webview-id', + toString: () => webviewUrl, + } as vscode.Uri; + + const mockExternalUri = { + toString: () => expectedExternalUrl, + } as vscode.Uri; + + vi.spyOn(vscode.Uri, 'parse').mockReturnValue(mockParsedUri); + vi.spyOn(vscode.env, 'asExternalUri').mockResolvedValue(mockExternalUri); + + const result = await getPublicUrl(webviewUrl); + + expect(result).toBe(expectedExternalUrl); + }); + }); + + describe('getExtensionVersion', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return the version from the extension package.json', () => { + const mockExtension = { + packageJSON: { + version: '1.2.3', + }, + } as vscode.Extension; + + vi.spyOn(vscode.extensions, 'getExtension').mockReturnValue(mockExtension); + + const result = getExtensionVersion(); + + expect(result).toBe('1.2.3'); + }); + + it('should return empty string if extension is not found', () => { + vi.spyOn(vscode.extensions, 'getExtension').mockReturnValue(undefined); + + const result = getExtensionVersion(); + + expect(result).toBe(''); + }); + + it('should return empty string if packageJSON is not available', () => { + const mockExtension = { + packageJSON: undefined, + } as unknown as vscode.Extension; + + vi.spyOn(vscode.extensions, 'getExtension').mockReturnValue(mockExtension); + + const result = getExtensionVersion(); + + expect(result).toBe(''); + }); + + it('should return empty string if version is not in packageJSON', () => { + const mockExtension = { + packageJSON: { + version: undefined, + }, + } as vscode.Extension; + + vi.spyOn(vscode.extensions, 'getExtension').mockReturnValue(mockExtension); + + const result = getExtensionVersion(); + + // Note: The function returns undefined when version is not present, + // which technically violates the return type signature + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/apps/vs-code-designer/src/app/utils/__test__/reportAnIssue.test.ts b/apps/vs-code-designer/src/app/utils/__test__/reportAnIssue.test.ts index 7b76a603801..7a081ac8029 100644 --- a/apps/vs-code-designer/src/app/utils/__test__/reportAnIssue.test.ts +++ b/apps/vs-code-designer/src/app/utils/__test__/reportAnIssue.test.ts @@ -43,9 +43,6 @@ describe('reportAnIssue', () => { get: vi.fn((key: string) => { const settings = { dataMapperVersion: '1.0.0', - validateFuncCoreTools: true, - autoRuntimeDependenciesPath: '/path/to/deps', - autoRuntimeDependenciesValidationAndInstallation: false, parameterizeConnectionsInProjectLoad: true, }; return settings[key as keyof typeof settings]; @@ -189,7 +186,6 @@ describe('reportAnIssue', () => { const decodedLink = decodeURIComponent(link); expect(decodedLink).toContain('Extension version: 1.0.0'); - expect(decodedLink).toContain('Extension bundle version: 1.2.3'); expect(decodedLink).toContain('OS: darwin'); expect(decodedLink).toContain('Product: Visual Studio Code'); expect(decodedLink).toContain('Product version: 1.85.0'); @@ -222,9 +218,6 @@ describe('reportAnIssue', () => { expect(decodedLink).toContain('Settings'); expect(decodedLink).toContain('dataMapperVersion'); - expect(decodedLink).toContain('validateFuncCoreTools'); - expect(decodedLink).toContain('autoRuntimeDependenciesPath'); - expect(decodedLink).toContain('autoRuntimeDependenciesValidationAndInstallation'); expect(decodedLink).toContain('parameterizeConnectionsInProjectLoad'); }); @@ -356,7 +349,6 @@ describe('reportAnIssue', () => { test('should handle missing extension versions', async () => { const originalExtensionVersion = ext.extensionVersion; - const originalBundleVersion = ext.latestBundleVersion; (ext as any).extensionVersion = undefined; (ext as any).latestBundleVersion = undefined; @@ -365,11 +357,9 @@ describe('reportAnIssue', () => { const decodedLink = decodeURIComponent(link); expect(decodedLink).toContain('Extension version: unknown'); - expect(decodedLink).toContain('Extension bundle version: unknown'); // Restore original values (ext as any).extensionVersion = originalExtensionVersion; - (ext as any).latestBundleVersion = originalBundleVersion; }); test('should handle different OS platforms', async () => { diff --git a/apps/vs-code-designer/src/app/utils/__test__/unitTestUtils.test.ts b/apps/vs-code-designer/src/app/utils/__test__/unitTestUtils.test.ts index 3f645589e88..910713940b1 100644 --- a/apps/vs-code-designer/src/app/utils/__test__/unitTestUtils.test.ts +++ b/apps/vs-code-designer/src/app/utils/__test__/unitTestUtils.test.ts @@ -18,6 +18,11 @@ vi.mock('axios', async () => { isAxiosError: vi.fn(), }; }); +// Mock getPublicUrl from extension utilities to avoid requiring a real VS Code environment +// Must be declared before importing the module that uses it (`../unitTests`). +vi.mock('../extension', () => ({ + getPublicUrl: vi.fn(async (url: string) => url), // no-op passthrough for tests +})); import { extractAndValidateRunId, validateRunId, @@ -1928,14 +1933,13 @@ namespace <%= LogicAppName %>.Tests }); describe('updateSolutionWithProject', () => { - const testDotnetBinaryPath = path.join('test', 'path', 'to', 'dotnet'); let pathExistsSpy: any; let executeCommandSpy: any; beforeEach(() => { vi.spyOn(ext.outputChannel, 'appendLog').mockImplementation(() => {}); vi.spyOn(util, 'promisify').mockImplementation((fn) => fn); - vi.spyOn(vscodeConfigSettings, 'getGlobalSetting').mockReturnValue(testDotnetBinaryPath); + vi.spyOn(vscodeConfigSettings, 'getGlobalSetting').mockReturnValue('dotnet'); executeCommandSpy = vi.spyOn(cpUtils, 'executeCommand').mockResolvedValue(''); }); @@ -1955,7 +1959,7 @@ namespace <%= LogicAppName %>.Tests expect(executeCommandSpy).toHaveBeenCalledWith( ext.outputChannel, testsDirectory, - `${testDotnetBinaryPath} sln "${path.join(testsDirectory, 'Tests.sln')}" add "${fakeLogicAppName}.csproj"` + `${'dotnet'} sln "${path.join(testsDirectory, 'Tests.sln')}" add "${fakeLogicAppName}.csproj"` ); }); @@ -1968,11 +1972,11 @@ namespace <%= LogicAppName %>.Tests await updateTestsSln(testsDirectory, logicAppCsprojPath); expect(executeCommandSpy).toHaveBeenCalledTimes(2); - expect(executeCommandSpy).toHaveBeenCalledWith(ext.outputChannel, testsDirectory, `${testDotnetBinaryPath} new sln -n Tests`); + expect(executeCommandSpy).toHaveBeenCalledWith(ext.outputChannel, testsDirectory, `${'dotnet'} new sln -n Tests`); expect(executeCommandSpy).toHaveBeenCalledWith( ext.outputChannel, testsDirectory, - `${testDotnetBinaryPath} sln "${path.join(testsDirectory, 'Tests.sln')}" add "${fakeLogicAppName}.csproj"` + `${'dotnet'} sln "${path.join(testsDirectory, 'Tests.sln')}" add "${fakeLogicAppName}.csproj"` ); }); }); diff --git a/apps/vs-code-designer/src/app/utils/binaries.ts b/apps/vs-code-designer/src/app/utils/binaries.ts deleted file mode 100644 index f13a4e3e961..00000000000 --- a/apps/vs-code-designer/src/app/utils/binaries.ts +++ /dev/null @@ -1,440 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { - DependencyVersion, - Platform, - autoRuntimeDependenciesValidationAndInstallationSetting, - autoRuntimeDependenciesPathSettingKey, - dependencyTimeoutSettingKey, - dotnetDependencyName, - funcPackageName, - defaultLogicAppsFolder, - dotNetBinaryPathSettingKey, - DependencyDefaultPath, - nodeJsBinaryPathSettingKey, - funcCoreToolsBinaryPathSettingKey, - funcDependencyName, - extensionBundleId, -} from '../../constants'; -import { ext } from '../../extensionVariables'; -import { localize } from '../../localize'; -import { onboardBinaries } from '../../onboarding'; -import { isNodeJsInstalled } from '../commands/nodeJs/validateNodeJsInstalled'; -import { executeCommand } from './funcCoreTools/cpUtils'; -import { getNpmCommand } from './nodeJs/nodeJsVersion'; -import { getGlobalSetting, getWorkspaceSetting, updateGlobalSetting } from './vsCodeConfig/settings'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { IGitHubReleaseInfo } from '@microsoft/vscode-extension-logic-apps'; -import axios from 'axios'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as semver from 'semver'; -import * as vscode from 'vscode'; - -import AdmZip = require('adm-zip'); -import { isNullOrUndefined, isString } from '@microsoft/logic-apps-shared'; -import { setFunctionsCommand } from './funcCoreTools/funcVersion'; -import { startAllDesignTimeApis, stopAllDesignTimeApis } from './codeless/startDesignTimeApi'; - -/** - * Download and Extracts dependency zip. - * @param {string} downloadUrl - download url. - * @param {string} targetFolder - Module name to check. - * @param {string} dependencyName - The Dedependency name. - * @param {string} folderName - Optional Folder name. Will default to dependency name if empty. - * @param {string} dotNetVersion - The .NET Major Version from CDN. - */ - -export async function downloadAndExtractDependency( - context: IActionContext, - downloadUrl: string, - targetFolder: string, - dependencyName: string, - folderName?: string, - dotNetVersion?: string -): Promise { - folderName = folderName || dependencyName; - const tempFolderPath = path.join(os.tmpdir(), defaultLogicAppsFolder, folderName); - targetFolder = path.join(targetFolder, folderName); - fs.mkdirSync(targetFolder, { recursive: true }); - - // Read and write permissions - fs.chmodSync(targetFolder, 0o777); - - const dependencyFileExtension = getCompressionFileExtension(downloadUrl); - const dependencyFilePath = path.join(tempFolderPath, `${dependencyName}${dependencyFileExtension}`); - - executeCommand(ext.outputChannel, undefined, 'echo', `Downloading dependency from: ${downloadUrl}`); - - axios.get(downloadUrl, { responseType: 'stream' }).then((response) => { - executeCommand(ext.outputChannel, undefined, 'echo', `Creating temporary folder... ${tempFolderPath}`); - fs.mkdirSync(tempFolderPath, { recursive: true }); - fs.chmodSync(tempFolderPath, 0o777); - - const writer = fs.createWriteStream(dependencyFilePath); - response.data.pipe(writer); - - writer.on('finish', async () => { - executeCommand(ext.outputChannel, undefined, 'echo', `Successfully downloaded ${dependencyName} dependency.`); - fs.chmodSync(dependencyFilePath, 0o777); - - // Extract to targetFolder - if (dependencyName === dotnetDependencyName) { - const version = dotNetVersion ?? semver.major(DependencyVersion.dotnet6); - process.platform === Platform.windows - ? await executeCommand( - ext.outputChannel, - undefined, - 'powershell -ExecutionPolicy Bypass -File', - dependencyFilePath, - '-InstallDir', - targetFolder, - '-Channel', - `${version}.0` - ) - : await executeCommand(ext.outputChannel, undefined, dependencyFilePath, '-InstallDir', targetFolder, '-Channel', `${version}.0`); - } else { - if (dependencyName === funcDependencyName || dependencyName === extensionBundleId) { - stopAllDesignTimeApis(); - } - await extractDependency(dependencyFilePath, targetFolder, dependencyName); - vscode.window.showInformationMessage(localize('successInstall', `Successfully installed ${dependencyName}`)); - if (dependencyName === funcDependencyName) { - // Add execute permissions for func and gozip binaries - if (process.platform !== Platform.windows) { - fs.chmodSync(`${targetFolder}/func`, 0o755); - fs.chmodSync(`${targetFolder}/gozip`, 0o755); - fs.chmodSync(`${targetFolder}/in-proc8/func`, 0o755); - fs.chmodSync(`${targetFolder}/in-proc6/func`, 0o755); - } - await setFunctionsCommand(); - await startAllDesignTimeApis(); - } else if (dependencyName === extensionBundleId) { - await startAllDesignTimeApis(); - } - } - // remove the temp folder. - fs.rmSync(tempFolderPath, { recursive: true }); - executeCommand(ext.outputChannel, undefined, 'echo', `Removed ${tempFolderPath}`); - }); - writer.on('error', async (error) => { - // log the error message the VSCode window and to telemetry. - const errorMessage = `Error downloading and extracting the ${dependencyName} zip file: ${error.message}`; - vscode.window.showErrorMessage(errorMessage); - context.telemetry.properties.error = errorMessage; - - // remove the target folder. - fs.rmSync(targetFolder, { recursive: true }); - await executeCommand(ext.outputChannel, undefined, 'echo', `[ExtractError]: Removed ${targetFolder}`); - }); - }); -} - -const getFunctionCoreToolVersionFromGithub = async (context: IActionContext, majorVersion: string): Promise => { - try { - const response: IGitHubReleaseInfo = await readJsonFromUrl( - 'https://api.github.com/repos/Azure/azure-functions-core-tools/releases/latest' - ); - const latestVersion = semver.valid(semver.coerce(response.tag_name)); - context.telemetry.properties.latestVersionSource = 'github'; - context.telemetry.properties.latestGithubVersion = response.tag_name; - if (checkMajorVersion(latestVersion, majorVersion)) { - return latestVersion; - } - throw new Error( - localize( - 'latestVersionNotFound', - 'Latest version of Azure Functions Core Tools not found for major version {0}. Latest version is {1}.', - majorVersion, - latestVersion - ) - ); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : isString(error) ? error : 'Unknown error'; - context.telemetry.properties.latestVersionSource = 'fallback'; - context.telemetry.properties.errorLatestFunctionCoretoolsVersion = `Error getting latest function core tools version from github: ${errorMessage}`; - return DependencyVersion.funcCoreTools; - } -}; - -export async function getLatestFunctionCoreToolsVersion(context: IActionContext, majorVersion?: string): Promise { - context.telemetry.properties.funcCoreTools = majorVersion; - - if (!majorVersion) { - context.telemetry.properties.latestVersionSource = 'fallback'; - return DependencyVersion.funcCoreTools; - } - - // Use npm to find newest func core tools version - const hasNodeJs = await isNodeJsInstalled(); - if (hasNodeJs) { - context.telemetry.properties.latestVersionSource = 'node'; - try { - const npmCommand = getNpmCommand(); - const latestVersion = (await executeCommand(undefined, undefined, `${npmCommand}`, 'view', funcPackageName, 'version'))?.trim(); - if (checkMajorVersion(latestVersion, majorVersion)) { - return latestVersion; - } - } catch (error) { - context.telemetry.properties.errorLatestFunctionCoretoolsVersion = `Error executing npm command to get latest function core tools version: ${error}`; - } - } - return await getFunctionCoreToolVersionFromGithub(context, majorVersion); -} - -/** - * 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 { - context.telemetry.properties.dotNetMajorVersion = majorVersion; - - if (majorVersion) { - return await readJsonFromUrl('https://api.github.com/repos/dotnet/sdk/releases') - .then((response: IGitHubReleaseInfo[]) => { - context.telemetry.properties.latestVersionSource = 'github'; - 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) && - (isNullOrUndefined(latestVersion) || semver.gt(releaseVersion, latestVersion)) - ) { - latestVersion = releaseVersion; - } - } - return latestVersion; - }) - .catch((error) => { - context.telemetry.properties.latestVersionSource = 'fallback'; - context.telemetry.properties.errorNewestDotNetVersion = `Error getting latest .NET SDK version: ${error}`; - return DependencyVersion.dotnet6; - }); - } - - context.telemetry.properties.latestVersionSource = 'fallback'; - return DependencyVersion.dotnet6; -} - -export async function getLatestNodeJsVersion(context: IActionContext, majorVersion?: string): Promise { - context.telemetry.properties.nodeMajorVersion = majorVersion; - - if (majorVersion) { - return await readJsonFromUrl('https://api.github.com/repos/nodejs/node/releases') - .then((response: IGitHubReleaseInfo[]) => { - context.telemetry.properties.latestVersionSource = 'github'; - for (const releaseInfo of response) { - const releaseVersion = semver.valid(semver.coerce(releaseInfo.tag_name)); - context.telemetry.properties.latestGithubVersion = releaseInfo.tag_name; - if (checkMajorVersion(releaseVersion, majorVersion)) { - return releaseVersion; - } - } - }) - .catch((error) => { - context.telemetry.properties.latestNodeJSVersion = 'fallback'; - context.telemetry.properties.errorLatestNodeJsVersion = `Error getting latest Node JS version: ${error}`; - return DependencyVersion.nodeJs; - }); - } - - context.telemetry.properties.latestNodeJSVersion = 'fallback'; - return DependencyVersion.nodeJs; -} - -export function getNodeJsBinariesReleaseUrl(version: string, osPlatform: string, arch: string): string { - if (osPlatform === 'win') { - return `https://nodejs.org/dist/v${version}/node-v${version}-${osPlatform}-${arch}.zip`; - } - - return `https://nodejs.org/dist/v${version}/node-v${version}-${osPlatform}-${arch}.tar.gz`; -} - -export function getFunctionCoreToolsBinariesReleaseUrl(version: string, osPlatform: string, arch: string): string { - return `https://github.com/Azure/azure-functions-core-tools/releases/download/${version}/Azure.Functions.Cli.${osPlatform}-${arch}.${version}.zip`; -} - -export function getDotNetBinariesReleaseUrl(): string { - return process.platform === Platform.windows ? 'https://dot.net/v1/dotnet-install.ps1' : 'https://dot.net/v1/dotnet-install.sh'; -} - -export function getCpuArchitecture() { - switch (process.arch) { - case 'x64': - case 'arm64': - return process.arch; - - default: - throw new Error(localize('UnsupportedCPUArchitecture', `Unsupported CPU architecture: ${process.arch}`)); - } -} - -/** - * Checks if binaries folder directory path exists. - * @param dependencyName The name of the dependency. - * @returns true if expected binaries folder directory path exists - */ -export function binariesExist(dependencyName: string): boolean { - if (!useBinariesDependencies()) { - return false; - } - const binariesLocation = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - const binariesPath = path.join(binariesLocation, dependencyName); - const binariesExist = fs.existsSync(binariesPath); - - executeCommand(ext.outputChannel, undefined, 'echo', `${dependencyName} Binaries: ${binariesPath}`); - return binariesExist; -} - -async function readJsonFromUrl(url: string): Promise { - try { - const response = await axios.get(url); - if (response.status === 200) { - return response.data; - } - throw new Error(`Request failed with status: ${response.status}`); - } catch (error) { - vscode.window.showErrorMessage(`Error reading JSON from URL ${url} : ${error.message}`); - throw error; - } -} - -function getCompressionFileExtension(binariesUrl: string): string { - if (binariesUrl.endsWith('.zip')) { - return '.zip'; - } - - if (binariesUrl.endsWith('.tar.gz')) { - return '.tar.gz'; - } - - if (binariesUrl.endsWith('.tar.xz')) { - return '.tar.xz'; - } - - if (binariesUrl.endsWith('.ps1')) { - return '.ps1'; - } - - if (binariesUrl.endsWith('.sh')) { - return '.sh'; - } - - throw new Error(localize('UnsupportedCompressionFileExtension', `Unsupported compression file extension: ${binariesUrl}`)); -} - -function cleanDirectory(targetFolder: string): void { - // Read all files/folders in targetFolder - const entries = fs.readdirSync(targetFolder); - for (const entry of entries) { - const entryPath = path.join(targetFolder, entry); - // Remove files or directories recursively - fs.rmSync(entryPath, { recursive: true, force: true }); - } -} - -async function extractDependency(dependencyFilePath: string, targetFolder: string, dependencyName: string): Promise { - // Clear targetFolder's contents without deleting the folder itself - // TODO(aeldridge): It is possible there is a lock on a file in targetFolder, should be handled. - cleanDirectory(targetFolder); - await executeCommand(ext.outputChannel, undefined, 'echo', `Extracting ${dependencyFilePath}`); - try { - if (dependencyFilePath.endsWith('.zip')) { - const zip = new AdmZip(dependencyFilePath); - await zip.extractAllTo(targetFolder, /* overwrite */ true, /* Permissions */ true); - } else { - await executeCommand(ext.outputChannel, undefined, 'tar', '-xzvf', dependencyFilePath, '-C', targetFolder); - } - extractContainerFolder(targetFolder); - await executeCommand(ext.outputChannel, undefined, 'echo', `Extraction ${dependencyName} successfully completed.`); - } catch (error) { - throw new Error(`Error extracting ${dependencyName}: ${error}`); - } -} - -/** - * 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); -} - -/** - * Cleans up by removing Container Folder: - * path/to/folder/container/files --> /path/to/folder/files - * @param targetFolder - */ -function extractContainerFolder(targetFolder: string) { - const extractedContents = fs.readdirSync(targetFolder); - if (extractedContents.length === 1 && fs.statSync(path.join(targetFolder, extractedContents[0])).isDirectory()) { - const containerFolderPath = path.join(targetFolder, extractedContents[0]); - const containerContents = fs.readdirSync(containerFolderPath); - containerContents.forEach((content) => { - const contentPath = path.join(containerFolderPath, content); - const destinationPath = path.join(targetFolder, content); - fs.renameSync(contentPath, destinationPath); - }); - - if (fs.readdirSync(containerFolderPath).length === 0) { - fs.rmSync(containerFolderPath, { recursive: true }); - } - } -} - -/** - * Gets dependency timeout setting value from workspace settings. - * @param {IActionContext} context - Command context. - * @returns {number} Timeout value in seconds. - */ -export function getDependencyTimeout(): number { - const dependencyTimeoutValue: number | undefined = getWorkspaceSetting(dependencyTimeoutSettingKey); - const timeoutInSeconds = Number(dependencyTimeoutValue); - if (Number.isNaN(timeoutInSeconds)) { - throw new Error( - localize( - 'invalidSettingValue', - 'The setting "{0}" must be a number, but instead found "{1}".', - dependencyTimeoutValue, - dependencyTimeoutValue - ) - ); - } - - return timeoutInSeconds; -} - -/** - * Prompts warning message to decide the auto validation/installation of dependency binaries. - * @param {IActionContext} context - Activation context. - */ -export async function installBinaries(context: IActionContext) { - const useBinaries = useBinariesDependencies(); - - if (useBinaries) { - await onboardBinaries(context); - context.telemetry.properties.autoRuntimeDependenciesValidationAndInstallationSetting = 'true'; - } else { - await updateGlobalSetting(dotNetBinaryPathSettingKey, DependencyDefaultPath.dotnet); - await updateGlobalSetting(nodeJsBinaryPathSettingKey, DependencyDefaultPath.node); - await updateGlobalSetting(funcCoreToolsBinaryPathSettingKey, DependencyDefaultPath.funcCoreTools); - context.telemetry.properties.autoRuntimeDependenciesValidationAndInstallationSetting = 'false'; - } -} - -/** - * Returns boolean to determine if workspace uses binaries dependencies. - */ -export const useBinariesDependencies = (): boolean => { - const binariesInstallation = getGlobalSetting(autoRuntimeDependenciesValidationAndInstallationSetting); - return !!binariesInstallation; -}; diff --git a/apps/vs-code-designer/src/app/utils/bundleFeed.ts b/apps/vs-code-designer/src/app/utils/bundleFeed.ts index 0f082252458..360a9e7c639 100644 --- a/apps/vs-code-designer/src/app/utils/bundleFeed.ts +++ b/apps/vs-code-designer/src/app/utils/bundleFeed.ts @@ -2,332 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { defaultVersionRange, extensionBundleId, localSettingsFileName, defaultExtensionBundlePathValue } from '../../constants'; -import { getLocalSettingsJson } from './appSettings/localSettings'; -import { downloadAndExtractDependency } from './binaries'; -import { getJsonFeed } from './feed'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { IBundleDependencyFeed, IBundleFeed, IBundleMetadata, IHostJsonV2 } from '@microsoft/vscode-extension-logic-apps'; -import * as path from 'path'; -import * as semver from 'semver'; import * as vscode from 'vscode'; import { localize } from '../../localize'; import { ext } from '../../extensionVariables'; -import { getFunctionsCommand } from './funcCoreTools/funcVersion'; -import * as fse from 'fs-extra'; import { executeCommand } from './funcCoreTools/cpUtils'; -/** - * Gets bundle extension feed. - * @param {IActionContext} context - Command context. - * @param {IBundleMetadata | undefined} bundleMetadata - Bundle meta data. - * @returns {Promise} Returns bundle extension object. - */ -async function getBundleFeed(context: IActionContext, bundleMetadata: IBundleMetadata | undefined): Promise { - const bundleId: string = (bundleMetadata && bundleMetadata.id) || extensionBundleId; - - const envVarUri: string | undefined = process.env.FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI; - // Only use an aka.ms link for the most common case, otherwise we will dynamically construct the url - let url: string; - if (!envVarUri && bundleId === extensionBundleId) { - url = 'https://aka.ms/AAqvc78'; - } else { - const baseUrl: string = envVarUri || 'https://cdn.functions.azure.com/public'; - url = `${baseUrl}/ExtensionBundles/${bundleId}/index-v2.json`; - } - - return getJsonFeed(context, url); -} - -/** - * Gets Workflow bundle extension feed. - * @param {IActionContext} context - Command context. - * @param {IBundleMetadata | undefined} bundleMetadata - Bundle meta data. - * @returns {Promise} Returns bundle extension object. - */ -async function getWorkflowBundleFeed(context: IActionContext): Promise { - const envVarUri: string | undefined = process.env.FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI; - const baseUrl: string = envVarUri || 'https://cdn.functions.azure.com/public'; - const url = `${baseUrl}/ExtensionBundles/${extensionBundleId}/index-v2.json`; - - return getJsonFeed(context, url); -} - -/** - * Gets extension bundle dependency feed. - * @param {IActionContext} context - Command context. - * @param {IBundleMetadata | undefined} bundleMetadata - Bundle meta data. - * @returns {Promise} Returns bundle extension object. - */ -async function getBundleDependencyFeed( - context: IActionContext, - bundleMetadata: IBundleMetadata | undefined -): Promise { - const bundleId: string = (bundleMetadata && bundleMetadata?.id) || extensionBundleId; - const projectPath: string | undefined = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : null; - let envVarUri: string | undefined = process.env.FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI; - if (projectPath) { - envVarUri = (await getLocalSettingsJson(context, path.join(projectPath, localSettingsFileName)))?.Values - ?.FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI; - } - - const baseUrl: string = envVarUri || 'https://cdn.functions.azure.com/public'; - const url = `${baseUrl}/ExtensionBundles/${bundleId}/dependency.json`; - return getJsonFeed(context, url); -} - -/** - * Gets latest bundle extension version range. - * @param {IActionContext} context - Command context. - * @returns {Promise} Returns lates version range. - */ -export async function getLatestVersionRange(context: IActionContext): Promise { - const feed: IBundleFeed = await getBundleFeed(context, undefined); - return feed.defaultVersionRange; -} - -/** - * Gets latest bundle extension dependencies versions. - * @param {IActionContext} context - Command context. - * @returns {Promise} Returns dependency versions. - */ -export async function getDependenciesVersion(context: IActionContext): Promise { - const feed: IBundleDependencyFeed = await getBundleDependencyFeed(context, undefined); - return feed; -} - -/** - * Add bundle extension version to host.json configuration. - * @param {IActionContext} context - Command context. - * @param {IHostJsonV2} hostJson - Host.json configuration. - */ -export async function addDefaultBundle(context: IActionContext, hostJson: IHostJsonV2): Promise { - let versionRange: string; - try { - versionRange = await getLatestVersionRange(context); - } catch { - versionRange = defaultVersionRange; - } - - hostJson.extensionBundle = { - id: extensionBundleId, - version: versionRange, - }; -} - -/** - * Gets bundle extension zip. Microsoft.Azure.Functions.ExtensionBundle.Workflows.. - * @param {IActionContext} context - Command context. - * @param {string} extensionVersion - Bundle Extension Version. - * @returns {string} Returns bundle extension zip url. - */ -async function getExtensionBundleZip(context: IActionContext, extensionVersion: string): Promise { - let envVarUri: string | undefined = process.env.FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI; - const projectPath: string | undefined = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : null; - if (projectPath) { - envVarUri = (await getLocalSettingsJson(context, path.join(projectPath, localSettingsFileName)))?.Values - ?.FUNCTIONS_EXTENSIONBUNDLE_SOURCE_URI; - } - const baseUrl: string = envVarUri || 'https://cdn.functions.azure.com/public'; - const url = `${baseUrl}/ExtensionBundles/${extensionBundleId}/${extensionVersion}/${extensionBundleId}.${extensionVersion}_any-any.zip`; - - return url; -} - -/** - * Gets the Extension Bundle Versions iterating through the default extension bundle path directory. - * @param {string} directoryPath - extension bundle path directory. - * @returns {string[]} Returns the list of versions. - */ -async function getExtensionBundleVersionFolders(directoryPath: string): Promise { - if (!(await fse.pathExists(directoryPath))) { - return []; - } - const directoryContents = fse.readdirSync(directoryPath); - - // Filter only the folders with valid version names. - const folders = directoryContents.filter((item) => { - const itemPath = path.join(directoryPath, item); - return fse.statSync(itemPath).isDirectory() && semver.valid(item); - }); - - return folders; -} - -/** - * Download Microsoft.Azure.Functions.ExtensionBundle.Workflows. - * Destination: C:\Users\\.azure-functions-core-tools\Functions\ExtensionBundles\ - * @param {IActionContext} context - Command context. - * @returns {Promise} A boolean indicating whether the bundle was updated. - */ -export async function downloadExtensionBundle(context: IActionContext): Promise { - try { - const downloadExtensionBundleStartTime = Date.now(); - let envVarVer: string | undefined = process.env.AzureFunctionsJobHost_extensionBundle_version; - const projectPath: string | undefined = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : null; - if (projectPath) { - envVarVer = (await getLocalSettingsJson(context, path.join(projectPath, localSettingsFileName)))?.Values - ?.AzureFunctionsJobHost_extensionBundle_version; - } - - // Check for latest version at directory. - let latestLocalBundleVersion = '1.0.0'; - const localVersions = await getExtensionBundleVersionFolders(defaultExtensionBundlePathValue); - for (const localVersion of localVersions) { - latestLocalBundleVersion = semver.gt(latestLocalBundleVersion, localVersion) ? latestLocalBundleVersion : localVersion; - } - - context.telemetry.properties.envVariableExtensionBundleVersion = envVarVer; - if (envVarVer) { - if (semver.eq(envVarVer, latestLocalBundleVersion)) { - return false; - } - - const extensionBundleUrl = await getExtensionBundleZip(context, envVarVer); - await downloadAndExtractDependency(context, extensionBundleUrl, defaultExtensionBundlePathValue, extensionBundleId, envVarVer); - context.telemetry.measurements.downloadExtensionBundleDuration = (Date.now() - downloadExtensionBundleStartTime) / 1000; - context.telemetry.properties.didUpdateExtensionBundle = 'true'; - return true; - } - - // Check the latest from feed. - let latestFeedBundleVersion = '1.0.0'; - const feed: IBundleFeed = await getWorkflowBundleFeed(context); - for (const bundleVersion in feed.bundleVersions) { - latestFeedBundleVersion = semver.gt(latestFeedBundleVersion, bundleVersion) ? latestFeedBundleVersion : bundleVersion; - } - - context.telemetry.properties.latestBundleVersion = semver.gt(latestFeedBundleVersion, latestLocalBundleVersion) - ? latestFeedBundleVersion - : latestLocalBundleVersion; - - ext.defaultBundleVersion = context.telemetry.properties.latestBundleVersion; - ext.latestBundleVersion = context.telemetry.properties.latestBundleVersion; - - if (semver.gt(latestFeedBundleVersion, latestLocalBundleVersion)) { - const extensionBundleUrl = await getExtensionBundleZip(context, latestFeedBundleVersion); - await downloadAndExtractDependency( - context, - extensionBundleUrl, - defaultExtensionBundlePathValue, - extensionBundleId, - latestFeedBundleVersion - ); - - context.telemetry.measurements.downloadExtensionBundleDuration = (Date.now() - downloadExtensionBundleStartTime) / 1000; - context.telemetry.properties.didUpdateExtensionBundle = 'true'; - return true; - } - - context.telemetry.measurements.downloadExtensionBundleDuration = (Date.now() - downloadExtensionBundleStartTime) / 1000; - context.telemetry.properties.didUpdateExtensionBundle = 'false'; - return false; - } catch (error) { - const errorMessage = `Error downloading and extracting the Logic Apps Standard extension bundle: ${error.message}`; - context.telemetry.properties.errorMessage = errorMessage; - return false; - } -} - -/** - * Retrieves the latest version number of a bundle from the specified folder. - * @param {string} bundleFolder - The path to the folder containing the bundle. - * @returns The latest version number of the bundle. - * @throws An error if the bundle folder is empty. - */ -export const getLatestBundleVersion = async (bundleFolder: string) => { - let bundleVersionNumber = '0.0.0'; - - const bundleFolders = await fse.readdir(bundleFolder); - if (bundleFolders.length === 0) { - throw new Error(localize('bundleMissingError', 'Extension bundle could not be found.')); - } - - for (const file of bundleFolders) { - const filePath: string = path.join(bundleFolder, file); - if (await (await fse.stat(filePath)).isDirectory()) { - bundleVersionNumber = getMaxVersion(bundleVersionNumber, file); - } - } - - return bundleVersionNumber; -}; - -/** - * Compares and gets biggest extension bundle version. - * @param version1 - Extension bundle version. - * @param version2 - Extension bundle version. - * @returns {string} Biggest extension bundle version. - */ -function getMaxVersion(version1, version2): string { - let maxVersion = ''; - let arr1 = version1.split('.'); - let arr2 = version2.split('.'); - - arr1 = arr1.map(Number); - arr2 = arr2.map(Number); - - const arr1Size = arr1.length; - const arr2Size = arr2.length; - - if (arr1Size > arr2Size) { - for (let i = arr2Size; i < arr1Size; i++) { - arr2.push(0); - } - } else { - for (let i = arr1Size; i < arr2Size; i++) { - arr1.push(0); - } - } - - for (let i = 0; i < arr1.length; i++) { - if (arr1[i] > arr2[i]) { - maxVersion = version1; - break; - } - if (arr2[i] > arr1[i]) { - maxVersion = version2; - break; - } - } - return maxVersion; -} - -/** - * Retrieves the highest version number of the extension bundle available in the bundle folder. - * - * This function locates the extension bundle folder, enumerates its subdirectories, - * and determines the maximum version number present among them. If no bundle is found, - * it throws an error. - * - * @returns {Promise} A promise that resolves to the highest bundle version number as a string (e.g., "1.2.3"). - * @throws {Error} If the extension bundle folder is missing or contains no subdirectories. - */ -export async function getBundleVersionNumber(): Promise { - const bundleFolderRoot = await getExtensionBundleFolder(); - const bundleFolder = path.join(bundleFolderRoot, extensionBundleId); - let bundleVersionNumber = '0.0.0'; - - const bundleFolders = await fse.readdir(bundleFolder); - if (bundleFolders.length === 0) { - throw new Error(localize('bundleMissingError', 'Extension bundle could not be found.')); - } - - for (const file of bundleFolders) { - const filePath: string = path.join(bundleFolder, file); - if (await (await fse.stat(filePath)).isDirectory()) { - bundleVersionNumber = getMaxVersion(bundleVersionNumber, file); - } - } - - return bundleVersionNumber; -} /** * Gets extension bundle folder path. * @returns {string} Extension bundle folder path. */ export async function getExtensionBundleFolder(): Promise { - const command = getFunctionsCommand(); + const command = 'func'; const outputChannel = ext.outputChannel; const workingDirectory = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; diff --git a/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts b/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts index 318bb3782d9..76dd1bf38e6 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts @@ -64,7 +64,7 @@ export async function getWorkflow( } export async function listWorkflows(node: SlotTreeItem, context: IActionContext): Promise[]> { - const url = `${node.id}/hostruntime${managementApiPrefix}/workflows?api-version=${workflowAppApiVersion}`; + const url = `${node.id}/hostruntime/${managementApiPrefix}/workflows?api-version=${workflowAppApiVersion}`; try { const response = await sendAzureRequest(url, context, 'GET', node.site.subscription); return response.parsedBody; diff --git a/apps/vs-code-designer/src/app/utils/codeless/common.ts b/apps/vs-code-designer/src/app/utils/codeless/common.ts index 976fc5b84d1..617a5e930b9 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/common.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/common.ts @@ -349,7 +349,7 @@ export function getWorkflowManagementBaseURI(node: RemoteWorkflowTreeItem): stri if (resourceManagerUri.endsWith('/')) { resourceManagerUri = resourceManagerUri.slice(0, -1); } - return `${resourceManagerUri}${node.parent.parent.id}/hostruntime${managementApiPrefix}`; + return `${resourceManagerUri}${node.parent.parent.id}/hostruntime/${managementApiPrefix}`; } /** diff --git a/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts b/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts index 3b879eeaffe..08af2dd080e 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/startDesignTimeApi.ts @@ -24,7 +24,6 @@ import { localize } from '../../../localize'; import { addOrUpdateLocalAppSettings, getLocalSettingsSchema } from '../appSettings/localSettings'; import { updateFuncIgnore } from '../codeless/common'; import { writeFormattedJson } from '../fs'; -import { getFunctionsCommand } from '../funcCoreTools/funcVersion'; import { getWorkspaceSetting, updateGlobalSetting } from '../vsCodeConfig/settings'; import { getWorkspaceLogicAppFolders } from '../workspace'; import { delay } from '../delay'; @@ -64,7 +63,7 @@ export async function startDesignTimeApi(projectPath: string): Promise { } const designTimeInst = ext.designTimeInstances.get(projectPath); - const url = `http://localhost:${designTimeInst.port}${designerStartApi}`; + const url = `http://localhost:${designTimeInst.port}/${designerStartApi}`; if (designTimeInst.isStarting && !isNewDesignTime) { await waitForDesignTimeStartUp(actionContext, projectPath, url); actionContext.telemetry.properties.isDesignTimeUp = 'true'; @@ -118,7 +117,7 @@ export async function startDesignTimeApi(projectPath: string): Promise { const cwd: string = designTimeDirectory.fsPath; const portArgs = `--port ${designTimeInst.port}`; - startDesignTimeProcess(ext.outputChannel, cwd, getFunctionsCommand(), 'host', 'start', portArgs); + startDesignTimeProcess(ext.outputChannel, cwd, 'func', 'host', 'start', portArgs); await waitForDesignTimeStartUp(actionContext, projectPath, url, true); actionContext.telemetry.properties.isDesignTimeUp = 'true'; @@ -128,9 +127,7 @@ export async function startDesignTimeApi(projectPath: string): Promise { if (data.extensionBundle) { const versionWithoutSpaces = data.extensionBundle.version.replace(/\s+/g, ''); const rangeWithoutSpaces = defaultVersionRange.replace(/\s+/g, ''); - if (data.extensionBundle.id === extensionBundleId && versionWithoutSpaces === rangeWithoutSpaces) { - ext.currentBundleVersion.set(projectPath, ext.latestBundleVersion); - } else if (data.extensionBundle.id === extensionBundleId && versionWithoutSpaces !== rangeWithoutSpaces) { + if (data.extensionBundle.id === extensionBundleId && versionWithoutSpaces !== rangeWithoutSpaces) { ext.currentBundleVersion.set(projectPath, extractPinnedVersion(data.extensionBundle.version) ?? data.extensionBundle.version); ext.pinnedBundleVersion.set(projectPath, true); } diff --git a/apps/vs-code-designer/src/app/utils/debug.ts b/apps/vs-code-designer/src/app/utils/debug.ts index e77826cce74..a40e11cfb4e 100644 --- a/apps/vs-code-designer/src/app/utils/debug.ts +++ b/apps/vs-code-designer/src/app/utils/debug.ts @@ -2,19 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FuncVersion, TargetFramework } from '@microsoft/vscode-extension-logic-apps'; +import type { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; +import { TargetFramework } from '@microsoft/vscode-extension-logic-apps'; import type { DebugConfiguration } from 'vscode'; -import { debugSymbolDll, extensionBundleId, extensionCommand } from '../../constants'; - +import { debugSymbolDll, EXTENSION_BUNDLE_VERSION, extensionBundleId, extensionCommand } from '../../constants'; import * as path from 'path'; -import { getBundleVersionNumber, getExtensionBundleFolder } from './bundleFeed'; +import { getExtensionBundleFolder } from './bundleFeed'; export async function getDebugSymbolDll(): Promise { const bundleFolderRoot = await getExtensionBundleFolder(); const bundleFolder = path.join(bundleFolderRoot, extensionBundleId); - const bundleVersionNumber = await getBundleVersionNumber(); - return path.join(bundleFolder, bundleVersionNumber, 'bin', debugSymbolDll); + return path.join(bundleFolder, EXTENSION_BUNDLE_VERSION, 'bin', debugSymbolDll); } /** @@ -37,7 +36,7 @@ export const getDebugConfiguration = ( name: `Run/Debug logic app with local function ${logicAppName}`, type: 'logicapp', request: 'launch', - funcRuntime: version === FuncVersion.v1 ? 'clr' : 'coreclr', + funcRuntime: 'coreclr', customCodeRuntime: customCodeTargetFramework === TargetFramework.Net8 ? 'coreclr' : 'clr', isCodeless: true, }; @@ -45,7 +44,7 @@ export const getDebugConfiguration = ( return { name: `Run/Debug logic app ${logicAppName}`, - type: version === FuncVersion.v1 ? 'clr' : 'coreclr', + type: 'coreclr', request: 'attach', processId: `\${command:${extensionCommand.pickProcess}}`, }; diff --git a/apps/vs-code-designer/src/app/utils/devContainer.ts b/apps/vs-code-designer/src/app/utils/devContainer.ts new file mode 100644 index 00000000000..a29aac1c6ed --- /dev/null +++ b/apps/vs-code-designer/src/app/utils/devContainer.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import type { IActionContext } from '@microsoft/vscode-azext-utils'; + +/** Heuristic check to determine if VS Code is already running inside a dev container or Codespace. */ +export function isInDevContainer(): boolean { + return ( + vscode.env.remoteName === 'dev-container' || process.env.CODESPACES === 'true' || !!process.env.DEVCONTAINER || !!process.env.CONTAINER + ); +} + +/** Returns the probable base path we should inspect for a .devcontainer directory. */ +export function getDevContainerBasePath(): string | undefined { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (workspaceFolders && workspaceFolders.length > 0) { + return workspaceFolders[0].uri.fsPath; + } + if (vscode.workspace.workspaceFile) { + return path.dirname(vscode.workspace.workspaceFile.fsPath); + } + return undefined; +} + +/** + * Checks if a .devcontainer folder exists at the directory that contains the .code-workspace file. + * We climb up ancestors (bounded) until we find a directory containing a .code-workspace file; if found, we only + * succeed when a .devcontainer folder is present in that same directory. If not found or .devcontainer missing, returns false. + */ +export function hasDevContainerFolder(basePath?: string): boolean { + if (!basePath) { + return false; + } + try { + const maxLevels = 5; // small bound to avoid deep traversal + let current = basePath; + for (let i = 0; i <= maxLevels; i++) { + if (dirHasWorkspaceFile(current)) { + return dirHasDevContainer(current); + } + const parent = path.dirname(current); + if (parent === current) { + break; // root + } + current = parent; + } + } catch { + // ignore errors, treat as not found + } + return false; +} + +function dirHasDevContainer(dir: string): boolean { + try { + const devcontainerDir = path.join(dir, '.devcontainer'); + return fs.existsSync(devcontainerDir) && fs.statSync(devcontainerDir).isDirectory(); + } catch { + return false; + } +} + +function dirHasWorkspaceFile(dir: string): boolean { + try { + const entries = fs.readdirSync(dir); + return entries.some((e) => e.endsWith('.code-workspace') && fs.statSync(path.join(dir, e)).isFile()); + } catch { + return false; + } +} + +/** + * Attempts to execute the Dev Containers extension reopen command if we have a .devcontainer folder & are not already inside one. + * Adds a telemetry property when provided a context. + */ +export async function tryReopenInDevContainer(context?: IActionContext): Promise { + if (isInDevContainer()) { + return false; + } + const basePath = getDevContainerBasePath(); + const hasFolder = hasDevContainerFolder(basePath); + if (context) { + context.telemetry.properties.attemptedDevContainerReopen = hasFolder ? 'true' : 'false'; + } + if (!hasFolder) { + return false; + } + try { + const info = findWorkspaceFileAndRoot(basePath); + if (info?.workspaceFilePath) { + if (context) { + context.telemetry.properties.devContainerWorkspaceArg = 'workspace-file'; + } + // Try opening the workspace directly in a container. + await vscode.commands.executeCommand('remote-containers.openWorkspace', vscode.Uri.file(info.workspaceFilePath)); + } else if (info?.rootDir) { + if (context) { + context.telemetry.properties.devContainerWorkspaceArg = 'root-dir'; + } + await vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(info.rootDir)); + } else { + if (context) { + context.telemetry.properties.devContainerWorkspaceArg = 'reopen-fallback'; + } + await vscode.commands.executeCommand('remote-containers.reopenInContainer'); + } + return true; + } catch (err) { + if (context) { + context.telemetry.properties.devContainerReopenError = err instanceof Error ? err.message : String(err); + } + return false; + } +} + +/** Attempt to locate a .code-workspace file starting from basePath or using VS Code's current workspace reference. */ +function findWorkspaceFileAndRoot(basePath?: string): { workspaceFilePath: string; rootDir: string } | undefined { + // Prefer VS Code's current workspace file if present. + if (vscode.workspace.workspaceFile) { + const ws = vscode.workspace.workspaceFile.fsPath; + return { workspaceFilePath: ws, rootDir: path.dirname(ws) }; + } + if (!basePath) { + return undefined; + } + const maxLevels = 5; + let current = basePath; + for (let i = 0; i <= maxLevels; i++) { + const wsFile = firstWorkspaceFileInDir(current); + if (wsFile) { + return { workspaceFilePath: wsFile, rootDir: path.dirname(wsFile) }; + } + const parent = path.dirname(current); + if (parent === current) { + break; + } + current = parent; + } + return undefined; +} + +function firstWorkspaceFileInDir(dir: string): string | undefined { + try { + const entries = fs.readdirSync(dir); + for (const e of entries) { + if (e.endsWith('.code-workspace')) { + const full = path.join(dir, e); + if (fs.statSync(full).isFile()) { + return full; + } + } + } + } catch { + // ignore + } + return undefined; +} diff --git a/apps/vs-code-designer/src/app/utils/dotnet/dotnet.ts b/apps/vs-code-designer/src/app/utils/dotnet/dotnet.ts index ae6576953aa..fe30ad654a7 100644 --- a/apps/vs-code-designer/src/app/utils/dotnet/dotnet.ts +++ b/apps/vs-code-designer/src/app/utils/dotnet/dotnet.ts @@ -1,29 +1,12 @@ -/*--------------------------------------------------------------------------------------------- - * 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 { - DotnetVersion, - Platform, - autoRuntimeDependenciesPathSettingKey, - dotNetBinaryPathSettingKey, - dotnetDependencyName, - isolatedSdkName, -} from '../../../constants'; -import { ext } from '../../../extensionVariables'; +import { DotnetVersion, isolatedSdkName } from '../../../constants'; import { localize } from '../../../localize'; -import { executeCommand } from '../funcCoreTools/cpUtils'; import { runWithDurationTelemetry } from '../telemetry'; -import { getGlobalSetting, updateGlobalSetting, updateWorkspaceSetting } from '../vsCodeConfig/settings'; -import { findFiles, getWorkspaceLogicAppFolders } from '../workspace'; +import { findFiles } from '../workspace'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import { AzExtFsExtra } from '@microsoft/vscode-azext-utils'; import type { IWorkerRuntime } from '@microsoft/vscode-extension-logic-apps'; import { FuncVersion, ProjectLanguage } from '@microsoft/vscode-extension-logic-apps'; -import * as fs from 'fs'; import * as path from 'path'; -import * as semver from 'semver'; export class ProjectFile { public name: string; @@ -135,18 +118,6 @@ export async function getTemplateKeyFromProjFile( targetFramework = DotnetVersion.net8; break; } - case FuncVersion.v3: { - targetFramework = DotnetVersion.net3; - break; - } - case FuncVersion.v2: { - targetFramework = DotnetVersion.net2; - break; - } - case FuncVersion.v1: { - targetFramework = DotnetVersion.net48; - break; - } } if (projectPath && (await AzExtFsExtra.pathExists(projectPath))) { @@ -186,89 +157,3 @@ export function getTemplateKeyFromFeedEntry(runtimeInfo: IWorkerRuntime): string const isIsolated = runtimeInfo.sdk.name.toLowerCase() === isolatedSdkName.toLowerCase(); return getProjectTemplateKey(runtimeInfo.targetFramework, isIsolated); } - -export async function getLocalDotNetVersionFromBinaries(majorVersion?: string): Promise { - const binariesLocation = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - const sdkVersionFolder = path.join(binariesLocation, dotnetDependencyName, 'sdk'); - - if (isNullOrUndefined(majorVersion)) { - try { - const output: string = await executeCommand(ext.outputChannel, undefined, getDotNetCommand(), '--version'); - const version: string | null = semver.clean(output); - if (version) { - return version; - } - } catch { - return null; - } - } - - const files = fs.existsSync(sdkVersionFolder) ? fs.readdirSync(sdkVersionFolder, { withFileTypes: true }) : null; - if (Array.isArray(files)) { - const sdkFolders = files.filter((file) => file.isDirectory()).map((file) => file.name); - const version = semver.maxSatisfying(sdkFolders, `~${majorVersion}`); - if (version !== null) { - await executeCommand(ext.outputChannel, undefined, 'echo', 'Local binary .NET SDK version', version); - return version; - } - } - - return null; -} - -/** - * Get the nodejs binaries executable or use the system nodejs executable. - */ -export function getDotNetCommand(): string { - const command = getGlobalSetting(dotNetBinaryPathSettingKey); - return command; -} - -export async function setDotNetCommand(): Promise { - const binariesLocation = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - const dotNetBinariesPath = path.join(binariesLocation, dotnetDependencyName); - const binariesExist = fs.existsSync(dotNetBinariesPath); - let command = ext.dotNetCliPath; - if (binariesExist) { - // Explicit executable for tasks.json - command = - process.platform === Platform.windows - ? path.join(dotNetBinariesPath, `${ext.dotNetCliPath}.exe`) - : path.join(dotNetBinariesPath, `${ext.dotNetCliPath}`); - const newPath = `${dotNetBinariesPath}${path.delimiter}\${env:PATH}`; - fs.chmodSync(dotNetBinariesPath, 0o777); - - try { - const workspaceLogicAppFolders = await getWorkspaceLogicAppFolders(); - for (const projectPath of workspaceLogicAppFolders) { - const pathEnv = { - PATH: newPath, - }; - - // Required for dotnet cli in VSCode Terminal - switch (process.platform) { - case Platform.windows: { - await updateWorkspaceSetting('integrated.env.windows', pathEnv, projectPath, 'terminal'); - break; - } - - case Platform.linux: { - await updateWorkspaceSetting('integrated.env.linux', pathEnv, projectPath, 'terminal'); - break; - } - - case Platform.mac: { - await updateWorkspaceSetting('integrated.env.osx', pathEnv, projectPath, 'terminal'); - break; - } - } - // Required for CoreClr - await updateWorkspaceSetting('dotNetCliPaths', [dotNetBinariesPath], projectPath, 'omnisharp'); - } - } catch (error) { - console.log(error); - } - } - - await updateGlobalSetting(dotNetBinaryPathSettingKey, command); -} diff --git a/apps/vs-code-designer/src/app/utils/dotnet/executeDotnetTemplateCommand.ts b/apps/vs-code-designer/src/app/utils/dotnet/executeDotnetTemplateCommand.ts index be483db6b21..c87b8d2367d 100644 --- a/apps/vs-code-designer/src/app/utils/dotnet/executeDotnetTemplateCommand.ts +++ b/apps/vs-code-designer/src/app/utils/dotnet/executeDotnetTemplateCommand.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; -import { useBinariesDependencies } from '../binaries'; import { executeCommand, wrapArgInQuotes } from '../funcCoreTools/cpUtils'; -import { getDotNetCommand, getLocalDotNetVersionFromBinaries } from './dotnet'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import type { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; import * as path from 'path'; @@ -41,7 +39,7 @@ export async function executeDotnetTemplateCommand( return await executeCommand( undefined, workingDirectory, - getDotNetCommand(), + 'dotnet', wrapArgInQuotes(jsonDllPath), '--templateDir', wrapArgInQuotes(getDotnetTemplateDir(version, projTemplateKey)), @@ -69,15 +67,6 @@ export function getDotnetTemplateDir(version: FuncVersion, projTemplateKey: stri return path.join(ext.context.globalStorageUri.fsPath, version, projTemplateKey); } -/** - * Validates .NET is installed. - * @param {IActionContext} context - Command context. - */ -export async function validateDotnetInstalled(context: IActionContext): Promise { - // NOTE: Doesn't feel obvious that `getFramework` would validate dotnet is installed, hence creating a separate function named `validateDotnetInstalled` to export from this file - await getFramework(context, undefined); -} - /** * Gets .NET framework version. * @param {IActionContext} context - Command context. @@ -86,25 +75,10 @@ export async function validateDotnetInstalled(context: IActionContext): Promise< */ export async function getFramework(context: IActionContext, workingDirectory: string | undefined, isCodeful = false): Promise { if (!cachedFramework || isCodeful) { - let versions = ''; - const dotnetBinariesLocation = getDotNetCommand(); - - versions = useBinariesDependencies() ? await getLocalDotNetVersionFromBinaries() : versions; - - try { - versions += await executeCommand(undefined, workingDirectory, dotnetBinariesLocation, '--version'); - } catch { - // ignore - } - - try { - versions += await executeCommand(undefined, workingDirectory, dotnetBinariesLocation, '--list-sdks'); - } catch { - // ignore - } + const versions = '8'; // Prioritize "LTS", then "Current", then "Preview" - const netVersions: string[] = ['8', '6', '3', '2', '9', '10']; + const netVersions: string[] = ['8', '6']; const semVersions: SemVer[] = netVersions.map((v) => semVerCoerce(v) as SemVer); diff --git a/apps/vs-code-designer/src/app/utils/extension.ts b/apps/vs-code-designer/src/app/utils/extension.ts index d3c01a25eb3..055a2f634f7 100644 --- a/apps/vs-code-designer/src/app/utils/extension.ts +++ b/apps/vs-code-designer/src/app/utils/extension.ts @@ -23,3 +23,9 @@ export const getExtensionVersion = (): string => { return ''; }; + +export async function getPublicUrl(url: string) { + const local = vscode.Uri.parse(url); + const external = await vscode.env.asExternalUri(local); + return external.toString(); +} diff --git a/apps/vs-code-designer/src/app/utils/funcCoreTools/funcHostTask.ts b/apps/vs-code-designer/src/app/utils/funcCoreTools/funcHostTask.ts index f723de81b8d..81ffedc7732 100644 --- a/apps/vs-code-designer/src/app/utils/funcCoreTools/funcHostTask.ts +++ b/apps/vs-code-designer/src/app/utils/funcCoreTools/funcHostTask.ts @@ -31,7 +31,7 @@ export function isFuncHostTask(task: vscode.Task): boolean { const commandLine: string | undefined = task.execution && (task.execution as vscode.ShellExecution).commandLine; if (task.definition.type === 'shell') { const command = (task.execution as vscode.ShellExecution).command?.toString(); - const funcRegex = /\$\{config:azureLogicAppsStandard\.funcCoreToolsBinaryPath\}/; + const funcRegex = /func/; // check for args? return funcRegex.test(command); } diff --git a/apps/vs-code-designer/src/app/utils/funcCoreTools/funcVersion.ts b/apps/vs-code-designer/src/app/utils/funcCoreTools/funcVersion.ts index 92160c5d7b5..0481987bfac 100644 --- a/apps/vs-code-designer/src/app/utils/funcCoreTools/funcVersion.ts +++ b/apps/vs-code-designer/src/app/utils/funcCoreTools/funcVersion.ts @@ -2,21 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { - autoRuntimeDependenciesPathSettingKey, - funcCoreToolsBinaryPathSettingKey, - funcDependencyName, - funcVersionSetting, -} from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { localize } from '../../../localize'; -import { getGlobalSetting, getWorkspaceSettingFromAnyFolder, updateGlobalSetting } from '../vsCodeConfig/settings'; +import { funcVersionSetting } from '../../../constants'; +import { getWorkspaceSettingFromAnyFolder } from '../vsCodeConfig/settings'; import { executeCommand } from './cpUtils'; import { isNullOrUndefined } from '@microsoft/logic-apps-shared'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import { FuncVersion, latestGAVersion } from '@microsoft/vscode-extension-logic-apps'; -import * as fs from 'fs'; -import * as path from 'path'; import * as semver from 'semver'; /** @@ -90,7 +81,7 @@ export async function tryGetLocalFuncVersion(): Promise */ export async function getLocalFuncCoreToolsVersion(): Promise { try { - const output: string = await executeCommand(undefined, undefined, `${getFunctionsCommand()}`, '--version'); + const output: string = await executeCommand(undefined, undefined, 'func', '--version'); const version: string | null = semver.clean(output); if (version) { return version; @@ -127,49 +118,3 @@ export function addLocalFuncTelemetry(context: IActionContext): void { context.telemetry.properties.funcCliVersion = 'none'; }); } - -/** - * Checks installed functions core tools version is supported. - * @param {string} version - Placeholder for input. - */ -export function checkSupportedFuncVersion(version: FuncVersion) { - if (version !== FuncVersion.v2 && version !== FuncVersion.v3 && version !== FuncVersion.v4) { - throw new Error( - localize( - 'versionNotSupported', - 'Functions core tools version "{0}" not supported. Only version "{1}" is currently supported for Codeless.', - version, - FuncVersion.v2 - ) - ); - } -} - -/** - * Get the functions binaries executable or use the system functions executable. - */ -export function getFunctionsCommand(): string { - const command = getGlobalSetting(funcCoreToolsBinaryPathSettingKey); - if (!command) { - throw Error('Functions Core Tools Binary Path Setting is empty'); - } - return command; -} - -export async function setFunctionsCommand(): Promise { - const binariesLocation = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - const funcBinariesPath = path.join(binariesLocation, funcDependencyName); - const binariesExist = fs.existsSync(funcBinariesPath); - let command = ext.funcCliPath; - if (binariesExist) { - command = path.join(funcBinariesPath, ext.funcCliPath); - fs.chmodSync(funcBinariesPath, 0o777); - - const funcExist = await fs.existsSync(command); - if (funcExist) { - fs.chmodSync(command, 0o777); - } - } - - await updateGlobalSetting(funcCoreToolsBinaryPathSettingKey, command); -} diff --git a/apps/vs-code-designer/src/app/utils/funcCoreTools/getBrewPackageName.ts b/apps/vs-code-designer/src/app/utils/funcCoreTools/getBrewPackageName.ts deleted file mode 100644 index 61d7bc4e03e..00000000000 --- a/apps/vs-code-designer/src/app/utils/funcCoreTools/getBrewPackageName.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { funcPackageName } from '../../../constants'; -import { executeCommand } from './cpUtils'; -import { tryGetMajorVersion } from './funcVersion'; -import { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; - -/** - * Gets functions core tools brew package name. - * @param {FuncVersion} version - Package version. - * @returns {string} Returns full package name for brew. - */ -export function getBrewPackageName(version: FuncVersion): string { - return `${funcPackageName}@${tryGetMajorVersion(version)}`; -} - -/** - * Gets installed functions core tools brew package. - * @param {FuncVersion} version - Package version. - * @returns {Promise} Returns installed full package name for brew. - */ -export async function tryGetInstalledBrewPackageName(version: FuncVersion): Promise { - const brewPackageName: string = getBrewPackageName(version); - if (await isBrewPackageInstalled(brewPackageName)) { - return brewPackageName; - } - let oldPackageName: string | undefined; - if (version === FuncVersion.v2) { - oldPackageName = funcPackageName; - } else if (version === FuncVersion.v3) { - oldPackageName = `${funcPackageName}-v3-preview`; - } - - if (oldPackageName && (await isBrewPackageInstalled(oldPackageName))) { - return oldPackageName; - } - return undefined; -} - -/** - * Checks if the package is installed via brew. - * @param {string} packageName - Package name. - * @returns {Promise} Returns true if the package is installed, otherwise returns false. - */ -async function isBrewPackageInstalled(packageName: string): Promise { - try { - await executeCommand(undefined, undefined, 'brew', 'ls', packageName); - return true; - } catch { - return false; - } -} diff --git a/apps/vs-code-designer/src/app/utils/funcCoreTools/getFuncPackageManagers.ts b/apps/vs-code-designer/src/app/utils/funcCoreTools/getFuncPackageManagers.ts deleted file mode 100644 index 816a369509f..00000000000 --- a/apps/vs-code-designer/src/app/utils/funcCoreTools/getFuncPackageManagers.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { funcPackageName, PackageManager, Platform } from '../../../constants'; -import { executeCommand } from './cpUtils'; -import { tryGetInstalledBrewPackageName } from './getBrewPackageName'; -import { FuncVersion } from '@microsoft/vscode-extension-logic-apps'; - -/** - * Gets package managers installed in the system. - * @param {boolean} isFuncInstalled - Is functions core tools installed. - * @returns {Promise} Returns array of package managers. - */ -export async function getFuncPackageManagers(isFuncInstalled: boolean): Promise { - const result: PackageManager[] = []; - if (process.platform === Platform.mac) { - if (await hasBrew(isFuncInstalled)) { - result.push(PackageManager.brew); - } - } - // https://github.com/Microsoft/vscode-azurefunctions/issues/311 - if (process.platform !== Platform.linux) { - try { - isFuncInstalled - ? await executeCommand(undefined, undefined, 'npm', 'ls', '-g', funcPackageName) - : await executeCommand(undefined, undefined, 'npm', '--version'); - result.push(PackageManager.npm); - } catch { - // an error indicates no npm - } - } - return result; -} - -/** - * Checks if the system has brew installed. - * @param {boolean} isFuncInstalled - Is functions core tools installed. - * @returns {Promise} Returns true if the system has brew installed, otherwise returns false. - */ -async function hasBrew(isFuncInstalled: boolean): Promise { - for (const version of Object.values(FuncVersion)) { - if (version !== FuncVersion.v1) { - if (isFuncInstalled) { - const packageName: string | undefined = await tryGetInstalledBrewPackageName(version); - if (packageName) { - return true; - } - } else { - try { - await executeCommand(undefined, undefined, 'brew', '--version'); - return true; - } catch { - // an error indicates no brew - } - } - } - } - - return false; -} diff --git a/apps/vs-code-designer/src/app/utils/funcCoreTools/getNpmDistTag.ts b/apps/vs-code-designer/src/app/utils/funcCoreTools/getNpmDistTag.ts deleted file mode 100644 index a56561fd58c..00000000000 --- a/apps/vs-code-designer/src/app/utils/funcCoreTools/getNpmDistTag.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../localize'; -import { parseJson } from '../parseJson'; -import { sendRequestWithExtTimeout } from '../requestUtils'; -import { tryGetMajorVersion } from './funcVersion'; -import { HTTP_METHODS } from '@microsoft/logic-apps-shared'; -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import type { FuncVersion, INpmDistTag, IPackageMetadata } from '@microsoft/vscode-extension-logic-apps'; -import * as semver from 'semver'; - -/** - * Gets distribution tag of functions core tools npm package. - * @param {IActionContext} context - Command context. - * @param {FuncVersion} version - Functions core tools version. - * @returns {Promise} Returns core tools version to install. - */ -export async function getNpmDistTag(context: IActionContext, version: FuncVersion): Promise { - const npmRegistryUri = 'https://aka.ms/AA2qmnu'; - const response = await sendRequestWithExtTimeout(context, { url: npmRegistryUri, method: HTTP_METHODS.GET }); - - const packageMetadata: IPackageMetadata = parseJson(response.bodyAsText); - const majorVersion: string = tryGetMajorVersion(version); - - const validVersions: string[] = Object.keys(packageMetadata.versions).filter((v: string) => !!semver.valid(v)); - const maxVersion: string | null = semver.maxSatisfying(validVersions, majorVersion); - - if (!maxVersion) { - throw new Error(localize('noDistTag', 'Failed to retrieve NPM tag for version "{0}".', version)); - } - return { tag: majorVersion, value: maxVersion }; -} diff --git a/apps/vs-code-designer/src/app/utils/nodeJs/nodeJsVersion.ts b/apps/vs-code-designer/src/app/utils/nodeJs/nodeJsVersion.ts deleted file mode 100644 index c741e02d520..00000000000 --- a/apps/vs-code-designer/src/app/utils/nodeJs/nodeJsVersion.ts +++ /dev/null @@ -1,97 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import type { IActionContext } from '@microsoft/vscode-azext-utils'; -import { Platform, autoRuntimeDependenciesPathSettingKey, nodeJsBinaryPathSettingKey, nodeJsDependencyName } from '../../../constants'; -import { ext } from '../../../extensionVariables'; -import { executeCommand } from '../funcCoreTools/cpUtils'; -import { getGlobalSetting, updateGlobalSetting } from '../vsCodeConfig/settings'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as semver from 'semver'; -import { isString } from '@microsoft/logic-apps-shared'; -import { binariesExist } from '../binaries'; - -/** - * Executes nodejs version command and gets it from cli. - * @returns {Promise} Functions core tools version. - */ -export async function getLocalNodeJsVersion(context: IActionContext): Promise { - try { - const output: string = await executeCommand(undefined, undefined, `${getNodeJsCommand()}`, '--version'); - const version: string | null = semver.clean(output); - if (version) { - return version; - } - return null; - } catch (error) { - const errorMessage = error instanceof Error ? error.message : isString(error) ? error : 'Unknown error'; - context.telemetry.properties.error = errorMessage; - return null; - } -} - -/** - * Get the npm binaries executable or use the system npm executable. - */ -export function getNpmCommand(): string { - const binariesLocation = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - const nodeJsBinariesPath = path.join(binariesLocation, nodeJsDependencyName); - const binaries = binariesExist(nodeJsDependencyName); - let command = ext.npmCliPath; - if (binaries) { - // windows the executable is at root folder, linux & macos its in the bin - command = path.join(nodeJsBinariesPath, ext.npmCliPath); - if (process.platform !== Platform.windows) { - const nodeSubFolder = getNodeSubFolder(command); - command = path.join(nodeJsBinariesPath, nodeSubFolder, 'bin', ext.npmCliPath); - } - } - return command; -} - -/** - * Get the nodejs binaries executable or use the system nodejs executable. - */ -export function getNodeJsCommand(): string { - const command = getGlobalSetting(nodeJsBinaryPathSettingKey); - return command; -} - -export async function setNodeJsCommand(): Promise { - const binariesLocation = getGlobalSetting(autoRuntimeDependenciesPathSettingKey); - const nodeJsBinariesPath = path.join(binariesLocation, nodeJsDependencyName); - const binariesExist = fs.existsSync(nodeJsBinariesPath); - let command = ext.nodeJsCliPath; - if (binariesExist) { - // windows the executable is at root folder, linux & macos its in the bin - command = path.join(nodeJsBinariesPath, ext.nodeJsCliPath); - if (process.platform !== Platform.windows) { - const nodeSubFolder = getNodeSubFolder(command); - command = path.join(nodeJsBinariesPath, nodeSubFolder, 'bin', ext.nodeJsCliPath); - - fs.chmodSync(nodeJsBinariesPath, 0o777); - } - } - await updateGlobalSetting(nodeJsBinaryPathSettingKey, command); -} - -function getNodeSubFolder(directoryPath: string): string | null { - try { - const items = fs.readdirSync(directoryPath); - - for (const item of items) { - const itemPath = path.join(directoryPath, item); - const stats = fs.statSync(itemPath); - - if (stats.isDirectory() && item.includes('node')) { - return item; - } - } - } catch (error) { - console.error('Error:', error.message); - } - - return ''; // No 'node' subfolders found -} diff --git a/apps/vs-code-designer/src/app/utils/reportAnIssue.ts b/apps/vs-code-designer/src/app/utils/reportAnIssue.ts index 8be3f02f658..fd1f0a74d5f 100644 --- a/apps/vs-code-designer/src/app/utils/reportAnIssue.ts +++ b/apps/vs-code-designer/src/app/utils/reportAnIssue.ts @@ -21,13 +21,7 @@ const MAX_INLINE_MESSAGE_CHARS = 1000; const MAX_ISSUE_BODY_CHARS = 4000; // Whitelisted extension configuration settings -const SETTINGS_WHITELIST: string[] = [ - 'dataMapperVersion', - 'validateFuncCoreTools', - 'autoRuntimeDependenciesPath', - 'autoRuntimeDependenciesValidationAndInstallation', - 'parameterizeConnectionsInProjectLoad', -]; +const SETTINGS_WHITELIST: string[] = ['dataMapperVersion', 'parameterizeConnectionsInProjectLoad']; /** * Generates a "Report an Issue" link from the provided error context and opens it in the user's browser. @@ -94,7 +88,6 @@ function buildIssueBody(errorContext: IErrorHandlerContext, issue: IParsedError, body += `\nSession id: ${vscode.env.sessionId}`; } body += `\nExtension version: ${ext.extensionVersion ?? 'unknown'}`; - body += `\nExtension bundle version: ${ext.latestBundleVersion ?? 'unknown'}`; body += `\nOS: ${process.platform} (${os.type()} ${os.release()})`; body += `\nOS arch: ${os.arch()}`; body += `\nProduct: ${vscode.env.appName}`; diff --git a/apps/vs-code-designer/src/app/utils/startRuntimeApi.ts b/apps/vs-code-designer/src/app/utils/startRuntimeApi.ts index 47b48c14b35..f7e1ba8d8cb 100644 --- a/apps/vs-code-designer/src/app/utils/startRuntimeApi.ts +++ b/apps/vs-code-designer/src/app/utils/startRuntimeApi.ts @@ -20,7 +20,7 @@ import axios from 'axios'; import { localize } from '../../localize'; import { delay } from './delay'; import { findChildProcess } from '../commands/pickFuncProcess'; -import { getFunctionsCommand } from './funcCoreTools/funcVersion'; +import { getPublicUrl } from './extension'; export async function startRuntimeApi(projectPath: string): Promise { await callWithTelemetryAndErrorHandling('azureLogicAppsStandard.startRuntimeProcess', async (context: IActionContext) => { @@ -71,7 +71,7 @@ export async function startRuntimeApi(projectPath: string): Promise { try { ext.outputChannel.appendLog(localize('startingRuntime', 'Starting Runtime API for project: {0}', projectPath)); - startRuntimeProcess(projectPath, getFunctionsCommand(), 'host', 'start', `--port ${runtimeInst.port}`); + startRuntimeProcess(projectPath, 'func', 'host', 'start', `--port ${runtimeInst.port}`); await waitForRuntimeStartUp(context, projectPath, runtimeInst.port, true); context.telemetry.properties.isRuntimeUp = 'true'; } catch (error) { @@ -117,7 +117,8 @@ async function waitForRuntimeStartUp(context: IActionContext, projectPath: strin async function isRuntimeUp(port: number): Promise { try { - const url = `http://localhost:${port}${designerStartApi}`; + const baseUrl = await getPublicUrl(`http://localhost:${port}`); + const url = `${baseUrl}${designerStartApi}`; await axios.get(url); return Promise.resolve(true); } catch { diff --git a/apps/vs-code-designer/src/app/utils/telemetry.ts b/apps/vs-code-designer/src/app/utils/telemetry.ts index de51387291a..55e44d06819 100644 --- a/apps/vs-code-designer/src/app/utils/telemetry.ts +++ b/apps/vs-code-designer/src/app/utils/telemetry.ts @@ -53,12 +53,10 @@ export const logSubscriptions = async (context: IActionContext) => { export const logExtensionSettings = async (context: IActionContext) => { const settingsToLog = [ - 'autoRuntimeDependenciesValidationAndInstallation', 'autoStartAzurite', 'autoStartDesignTime', 'parameterizeConnectionsInProjectLoad', 'showStartDesignTimeMessage', - 'validateDotNetSDK', 'stopFuncTaskPostDebug', ]; try { diff --git a/apps/vs-code-designer/src/app/utils/unitTests.ts b/apps/vs-code-designer/src/app/utils/unitTests.ts index 21eb037b7c4..9321e55afd0 100644 --- a/apps/vs-code-designer/src/app/utils/unitTests.ts +++ b/apps/vs-code-designer/src/app/utils/unitTests.ts @@ -12,7 +12,6 @@ import type { UnitTestResult } from '@microsoft/vscode-extension-logic-apps'; import { toPascalCase } from '@microsoft/logic-apps-shared'; import { assetsFolderName, - dotNetBinaryPathSettingKey, saveUnitTestEvent, testMockOutputsDirectory, testsDirectoryName, @@ -23,7 +22,7 @@ import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { getWorkflowsInLocalProject } from './codeless/common'; import { executeCommand } from './funcCoreTools/cpUtils'; -import { getGlobalSetting } from './vsCodeConfig/settings'; +import { getPublicUrl } from './extension'; /** * Saves a unit test definition for a workflow to the file system. @@ -1185,7 +1184,7 @@ export async function getMockableOperationTypes(): Promise { localize('errorStandardResourcesApi', 'Design time port is undefined. Please retry once Azure Functions Core Tools has started.') ); } - const baseUrl = `http://localhost:${designTimePort}`; + const baseUrl = await getPublicUrl(`http://localhost:${designTimePort}`); const listMockableOperationsUrl = `${baseUrl}/runtime/webhooks/workflow/api/management/listMockableOperations`; ext.outputChannel.appendLog(localize('listMockableOperations', `Fetching unit test mockable operations at ${listMockableOperationsUrl}`)); try { @@ -1220,7 +1219,7 @@ export async function getMockableHttpOperationTypes(): Promise { localize('errorStandardResourcesApi', 'Design time port is undefined. Please retry once Azure Functions Core Tools has started.') ); } - const baseUrl = `http://localhost:${designTimePort}`; + const baseUrl = await getPublicUrl(`http://localhost:${designTimePort}`); const listMockableHttpOperationsUrl = `${baseUrl}/runtime/webhooks/workflow/api/management/listMockableHttpOperations`; ext.outputChannel.appendLog( localize('listMockableHttpOperations', `Fetching unit test mockable http operations at ${listMockableHttpOperationsUrl}`) @@ -1301,7 +1300,7 @@ export async function isMockableHttp(type: string): Promise { export async function updateTestsSln(testsDirectory: string, logicAppCsprojPath: string): Promise { const solutionName = 'Tests'; // This will create "Tests.sln" const solutionFile = path.join(testsDirectory, `${solutionName}.sln`); - const dotnetBinaryPath = getGlobalSetting(dotNetBinaryPathSettingKey); + const dotnetCommand = 'dotnet'; try { // Create a new solution file if it doesn't already exist. @@ -1309,14 +1308,14 @@ export async function updateTestsSln(testsDirectory: string, logicAppCsprojPath: ext.outputChannel.appendLog(`Solution file already exists at ${solutionFile}.`); } else { ext.outputChannel.appendLog(`Creating new solution file at ${solutionFile}...`); - await executeCommand(ext.outputChannel, testsDirectory, `${dotnetBinaryPath} new sln -n ${solutionName}`); + await executeCommand(ext.outputChannel, testsDirectory, `${dotnetCommand} new sln -n ${solutionName}`); ext.outputChannel.appendLog(`Solution file created: ${solutionFile}`); } // Compute the relative path from the tests directory to the Logic App .csproj. const relativeProjectPath = path.relative(testsDirectory, logicAppCsprojPath); ext.outputChannel.appendLog(`Adding project '${relativeProjectPath}' to solution '${solutionFile}'...`); - await executeCommand(ext.outputChannel, testsDirectory, `${dotnetBinaryPath} sln "${solutionFile}" add "${relativeProjectPath}"`); + await executeCommand(ext.outputChannel, testsDirectory, `${dotnetCommand} sln "${solutionFile}" add "${relativeProjectPath}"`); ext.outputChannel.appendLog('Project added to solution successfully.'); } catch (err) { ext.outputChannel.appendLog(`Error updating solution: ${err}`); diff --git a/apps/vs-code-designer/src/app/utils/vsCodeConfig/settings.ts b/apps/vs-code-designer/src/app/utils/vsCodeConfig/settings.ts index b859fb37f6b..2ac9619d36b 100644 --- a/apps/vs-code-designer/src/app/utils/vsCodeConfig/settings.ts +++ b/apps/vs-code-designer/src/app/utils/vsCodeConfig/settings.ts @@ -99,8 +99,8 @@ function getScope(fsPath: WorkspaceFolder | string | undefined): Uri | Workspace return isString(fsPath) ? Uri.file(fsPath) : fsPath; } -function osSupportsVersion(version: FuncVersion | undefined): boolean { - return version !== FuncVersion.v1 || process.platform === Platform.windows; +function osSupportsVersion(): boolean { + return process.platform === Platform.windows; } /** @@ -113,12 +113,9 @@ export async function promptForFuncVersion(context: IActionContext, message?: st const recommended: string = localize('recommended', '(Recommended)'); let picks: IAzureQuickPickItem[] = [ { label: 'Azure Functions v4', description: recommended, data: FuncVersion.v4 }, - { label: 'Azure Functions v3', data: FuncVersion.v3 }, - { label: 'Azure Functions v2', data: FuncVersion.v2 }, - { label: 'Azure Functions v1', data: FuncVersion.v1 }, ]; - picks = picks.filter((p) => osSupportsVersion(p.data)); + picks = picks.filter(() => osSupportsVersion()); picks.push({ label: localize('learnMore', '$(link-external) Learn more...'), description: '', data: undefined }); diff --git a/apps/vs-code-designer/src/app/utils/vsCodeConfig/tasks.ts b/apps/vs-code-designer/src/app/utils/vsCodeConfig/tasks.ts index cd9e0724283..9121765263b 100644 --- a/apps/vs-code-designer/src/app/utils/vsCodeConfig/tasks.ts +++ b/apps/vs-code-designer/src/app/utils/vsCodeConfig/tasks.ts @@ -183,21 +183,21 @@ async function overwriteTasksJson(context: IActionContext, projectPath: string): tasks: [ { label: 'generateDebugSymbols', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['${input:getDebugSymbolDll}'], type: 'process', problemMatcher: '$msCompile', }, { label: 'clean', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['clean', ...commonArgs], type: 'process', problemMatcher: '$msCompile', }, { label: 'build', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['build', ...commonArgs], type: 'process', dependsOn: 'clean', @@ -209,14 +209,14 @@ async function overwriteTasksJson(context: IActionContext, projectPath: string): }, { label: 'clean release', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: [...releaseArgs, ...commonArgs], type: 'process', problemMatcher: '$msCompile', }, { label: 'publish', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['publish', ...releaseArgs, ...commonArgs], type: 'process', dependsOn: 'clean release', @@ -226,7 +226,7 @@ async function overwriteTasksJson(context: IActionContext, projectPath: string): label: 'func: host start', dependsOn: 'build', type: 'shell', - command: '${config:azureLogicAppsStandard.funcCoreToolsBinaryPath}', + command: 'func', args: ['host', 'start'], options: { cwd: debugSubpath, @@ -253,14 +253,14 @@ async function overwriteTasksJson(context: IActionContext, projectPath: string): tasks: [ { label: 'generateDebugSymbols', - command: '${config:azureLogicAppsStandard.dotnetBinaryPath}', + command: 'dotnet', args: ['${input:getDebugSymbolDll}'], type: 'process', problemMatcher: '$msCompile', }, { type: 'shell', - command: '${config:azureLogicAppsStandard.funcCoreToolsBinaryPath}', + command: 'func', args: ['host', 'start'], options: { env: { diff --git a/apps/vs-code-designer/src/assets/container/Dockerfile b/apps/vs-code-designer/src/assets/container/Dockerfile new file mode 100644 index 00000000000..ea356dab200 --- /dev/null +++ b/apps/vs-code-designer/src/assets/container/Dockerfile @@ -0,0 +1,119 @@ +FROM mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm + +# ----------------------------- +# Extension bundle (Workflows) +# ----------------------------- +ARG EXTENSION_BUNDLE_VERSION=1.131.9 +ARG EXTENSION_BUNDLE_CDN_URL=https://functionscdn.azureedge.net/public +# NOTE: this folder name intentionally begins with a dot; don't prefix with "/." +ARG EXTENSION_BUNDLE_FOLDER_PATH=.azure-functions-core-tools/Functions/ExtensionBundles + +# ----------------------------- +# Azure Functions Core Tools +# ----------------------------- +ARG FUNCTIONS_CORE_TOOLS_VERSION=4.2.2 +ARG FUNCTIONS_CORE_TOOLS_OS_PLATFORM=linux +ARG FUNCTIONS_CORE_TOOLS_ARCH=x64 +ARG FUNCTIONS_CORE_TOOLS_FOLDER_PATH=.azurelogicapps/dependencies/FuncCoreTools + +# ----------------------------- +# .NET SDK versions +# ----------------------------- +ARG DOTNET_VERSION_8=8.0 +ARG DOTNET_VERSION_6=6.0 + +# ----------------------------- +# OS deps + install everything +# ----------------------------- +RUN set -eux; \ + apt-get update; \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates curl jq gnupg wget unzip \ + libc6 libicu72 libgssapi-krb5-2 libkrb5-3 zlib1g; \ + rm -rf /var/lib/apt/lists/* + +# ----------------------------- +# Install .NET SDK 8.0 and 6.0 +# ----------------------------- +RUN set -eux; \ + ARCH="$(dpkg --print-architecture)"; \ + if [ "$ARCH" = "amd64" ]; then \ + # AMD64: Use APT packages + wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O /tmp/packages-microsoft-prod.deb; \ + dpkg -i /tmp/packages-microsoft-prod.deb; \ + rm /tmp/packages-microsoft-prod.deb; \ + apt-get update; \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + dotnet-sdk-8.0 \ + dotnet-sdk-6.0; \ + rm -rf /var/lib/apt/lists/*; \ + elif [ "$ARCH" = "arm64" ]; then \ + # ARM64: Use manual installation script + curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 8.0 --install-dir /usr/share/dotnet; \ + curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 6.0 --install-dir /usr/share/dotnet; \ + ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet; \ + fi; \ + # Verify installations + dotnet --version; \ + dotnet --list-sdks + +# ----------------------------- +# Download & unpack extension bundle +# ----------------------------- +RUN set -eux; \ + echo "Using extension bundle version: ${EXTENSION_BUNDLE_VERSION}"; \ + EXTENSION_BUNDLE_FILENAME="Microsoft.Azure.Functions.ExtensionBundle.Workflows.${EXTENSION_BUNDLE_VERSION}_any-any.zip"; \ + wget "${EXTENSION_BUNDLE_CDN_URL}/ExtensionBundles/Microsoft.Azure.Functions.ExtensionBundle.Workflows/${EXTENSION_BUNDLE_VERSION}/${EXTENSION_BUNDLE_FILENAME}" -O "/tmp/${EXTENSION_BUNDLE_FILENAME}"; \ + mkdir -p "/${EXTENSION_BUNDLE_FOLDER_PATH}/Microsoft.Azure.Functions.ExtensionBundle.Workflows/${EXTENSION_BUNDLE_VERSION}"; \ + unzip -q "/tmp/${EXTENSION_BUNDLE_FILENAME}" -d "/${EXTENSION_BUNDLE_FOLDER_PATH}/Microsoft.Azure.Functions.ExtensionBundle.Workflows/${EXTENSION_BUNDLE_VERSION}"; \ + rm -f "/tmp/${EXTENSION_BUNDLE_FILENAME}"; \ + find "/${EXTENSION_BUNDLE_FOLDER_PATH}/" -type f -exec chmod 644 {} \; ; \ + # ensure scripts/binaries inside the bundle stay executable if any + find "/${EXTENSION_BUNDLE_FOLDER_PATH}/" -type f -name "*.sh" -exec chmod 755 {} \; || true + +# ----------------------------- +# Download & install Core Tools (func) +# ----------------------------- +RUN set -eux; \ + echo "Downloading Azure Functions Core Tools version: ${FUNCTIONS_CORE_TOOLS_VERSION}"; \ + FILENAME="Azure.Functions.Cli.${FUNCTIONS_CORE_TOOLS_OS_PLATFORM}-${FUNCTIONS_CORE_TOOLS_ARCH}.${FUNCTIONS_CORE_TOOLS_VERSION}.zip"; \ + wget "https://github.com/Azure/azure-functions-core-tools/releases/download/${FUNCTIONS_CORE_TOOLS_VERSION}/${FILENAME}" -O "/tmp/${FILENAME}"; \ + mkdir -p "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}"; \ + unzip -q "/tmp/${FILENAME}" -d "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}"; \ + rm -f "/tmp/${FILENAME}"; \ + chmod -R a+rX "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}"; \ + for executable in \ + "func" \ + "gozip" \ + "in-proc8/func" \ + "in-proc6/func"; do \ + if [ -f "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}/${executable}" ]; then \ + chmod 755 "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}/${executable}"; \ + fi; \ + done; \ + ln -sf "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}/func" /usr/local/bin/func; \ + ln -sf "/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}/func" /usr/bin/func + +# ----------------------------- +# Verify CLI is on PATH (skipped during build due to Rosetta issues on ARM) +# The func command will work when container runs, just not during build verification +# ----------------------------- + +# ----------------------------- +# Environment variables +# ----------------------------- +# Ensure Core Tools and .NET are discoverable for all users +ENV PATH=/${FUNCTIONS_CORE_TOOLS_FOLDER_PATH}:/usr/share/dotnet:$PATH + +# Set DOTNET environment variables +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \ + DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 \ + DOTNET_NOLOGO=1 \ + DOTNET_ROOT=/usr/share/dotnet + +# ----------------------------- +# Keep container running +# ----------------------------- +# This prevents the container from exiting immediately +# When used with devcontainers this will be overridden +CMD ["sleep", "infinity"] diff --git a/apps/vs-code-designer/src/assets/container/README.md b/apps/vs-code-designer/src/assets/container/README.md new file mode 100644 index 00000000000..49904d41eb0 --- /dev/null +++ b/apps/vs-code-designer/src/assets/container/README.md @@ -0,0 +1,324 @@ +# Logic Apps Development Container + +This directory contains the Docker configuration for the Logic Apps Standard Development container. + +## 📦 What's Inside + +**Current Image Configuration:** +- **Base Image**: `mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm` +- **Node.js**: 22 (Debian Bookworm) +- **.NET SDK**: 8.0 & 6.0 +- **Functions Core Tools**: 4.2.2 +- **Extension Bundle**: 1.131.9 +- **Platforms**: `linux/amd64`, `linux/arm64` + +**Docker Hub:** +- **Repository**: `carloscastrotrejo/logicapps-dev` +- **URL**: https://hub.docker.com/r/carloscastrotrejo/logicapps-dev + +--- + +## 🔄 How It Works: Complete Execution Flow + +Understanding how Dockerfile, and devcontainer.json work together: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 1. BUILD PHASE (One-time or when Dockerfile changes) │ +└─────────────────────────────────────────────────────────────┘ + │ + ├─> Dockerfile executes line by line + │ ├─> FROM: Pull base image (Node.js 22 Debian) + │ ├─> RUN: Install OS packages + │ ├─> RUN: Install .NET SDK 8.0 & 6.0 + │ ├─> RUN: Download Extension Bundle + │ ├─> RUN: Download & install Functions Core Tools + │ └─> ENV: Set environment variables (PATH, DOTNET_ROOT) + │ + └─> Output: Docker image ready to use + ✅ Node.js 22 + ✅ .NET SDK 8.0 & 6.0 + ✅ Functions Core Tools 4.2.2 + ✅ Extension Bundle 1.131.9 + +┌─────────────────────────────────────────────────────────────┐ +│ 2. DEVCONTAINER PHASE (When VS Code connects) │ +└─────────────────────────────────────────────────────────────┘ + │ + ├─> devcontainer.json applies + │ ├─> Install features (Azure CLI, PowerShell) + │ ├─> Install VS Code extensions + │ ├─> Apply VS Code settings + │ └─> Run postStartCommand (start Azurite) + │ + └─> Development environment ready! 🎉 + ✅ All tools from Dockerfile (Node, .NET, func) + ✅ Azure CLI & PowerShell installed + ✅ VS Code extensions loaded + ✅ Azurite running +``` + +### ⚡ Key Points + +- **Dockerfile** = Heavy, slow installations (cached in image) +- **devcontainer.json** = Quick dev tools & VS Code customization + +### 📊 What Gets Installed Where + +| Component | Where | Why | +|-----------|-------|-----| +| **.NET SDK 8.0 & 6.0** | Dockerfile | Slow to install, rarely changes | +| **Functions Core Tools** | Dockerfile | Core dependency, specific version | +| **Extension Bundle** | Dockerfile | Large download, rarely changes | +| **Azure CLI** | devcontainer.json | Quick install, dev-only tool | +| **PowerShell + Az** | devcontainer.json | Quick install, dev-only tool | +| **VS Code Extensions** | devcontainer.json | User-specific preferences | + +--- + +## 🌐 Multi-Platform Support + +The image is built using **Docker buildx** to create multi-platform images that work seamlessly on both Intel/AMD and ARM architectures. + +### Benefits + +✅ **Automatic Detection** - Docker pulls the correct architecture for your platform +✅ **Better Performance** - Native execution on both Intel and ARM processors +✅ **No Platform Warnings** - Eliminates "platform mismatch" warnings +✅ **Single Image Tag** - One tag works for all platforms + +### Platform Detection + +- **Intel/AMD Macs & PCs**: Automatically pulls `linux/amd64` +- **Apple Silicon (M1/M2/M3)**: Automatically pulls `linux/arm64` +- **ARM Servers**: Automatically pulls `linux/arm64` + +**No need to specify `--platform` - Docker handles it automatically!** + +--- + +## 🚀 For Developers: Using the Image + +### Pull the Pre-Built Image + +```bash +# Pull latest version (auto-detects your platform) +docker pull carloscastrotrejo/logicapps-dev:latest + +# Pull specific version +docker pull carloscastrotrejo/logicapps-dev:v1.0.0 + +# Verify the platform +docker image inspect carloscastrotrejo/logicapps-dev:latest | grep Architecture +``` + +### Use with Dev Containers + +Your `devcontainer.json` is already configured to build locally. To use the pre-built image instead: + +```json +{ + "name": "Logic Apps Standard Development", + "image": "carloscastrotrejo/logicapps-dev:latest", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "ghcr.io/devcontainers/features/azure-cli:1": { + "version": "latest" + }, + "ghcr.io/devcontainers/features/powershell:1": { + "version": "latest", + "modules": "Az" + } + } + // ... rest of your config +} +``` +--- + +## 🛠️ For Maintainers: Building & Publishing + +### Prerequisites + +1. Docker Desktop installed and running +2. Docker Hub account +3. Logged in: + ```bash + docker login + ``` + +### Quick Build & Push + +Use the provided script: + +```bash +# Push as latest +./build-and-push.sh + +# Push with specific version +./build-and-push.sh v1.0.0 + +# Push matching Extension Bundle version +./build-and-push.sh 1.131.9 +``` + +The script automatically: +1. Verifies Docker is running +2. Checks Docker Hub authentication +3. Sets up Docker buildx for multi-platform builds +4. Builds for **both amd64 and arm64** architectures +5. Pushes to Docker Hub + +**Build Time:** +- First build: ~8-10 minutes (downloading base layers) +- Subsequent builds: ~3-5 minutes (with cache) + +### Manual Build (Advanced) + +```bash +cd apps/vs-code-designer/src/assets/container/ + +# Create and use buildx builder (first time only) +docker buildx create --name multiplatform-builder --use +docker buildx inspect --bootstrap + +# Build for multiple platforms and push +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t carloscastrotrejo/logicapps-dev:v1.0.0 \ + -t carloscastrotrejo/logicapps-dev:latest \ + --push \ + . +``` + +### Single Platform Build (Testing) + +For faster local testing: + +```bash +# Build only for your current platform +docker build -t carloscastrotrejo/logicapps-dev:test . + +# Or specify a platform +docker build --platform linux/amd64 -t carloscastrotrejo/logicapps-dev:test . +``` + +### Verify Published Image + +```bash +# Inspect image details (shows supported platforms) +docker buildx imagetools inspect carloscastrotrejo/logicapps-dev:latest + +# Check locally pulled platform +docker image inspect carloscastrotrejo/logicapps-dev:latest | grep -A 3 "Architecture" +``` + +--- + +## 🔢 Version Management + +### Versioning Strategy + +Use semantic versioning aligned with the Extension Bundle version: + +```bash +./build-and-push.sh 1.131.9 # Matches EXTENSION_BUNDLE_VERSION +./build-and-push.sh v1.131.9 # With 'v' prefix +./build-and-push.sh latest # Latest tag only +``` + +### Updating the Image + +When updating versions: + +1. **Update `Dockerfile`** with new versions (Extension Bundle, Core Tools, .NET) +2. **Update this README** with new version numbers +3. **Build and push**: + ```bash + ./build-and-push.sh 1.132.0 + ``` +4. **Notify team** to pull the latest image + +--- + +## 🛠️ Troubleshooting + +### Not Logged In +```bash +docker login +``` + +### "no builder selected" Error +```bash +docker buildx create --name multiplatform-builder --use +docker buildx inspect --bootstrap +``` + +### Build Fails +```bash +# Check Docker is running +docker info + +# Clean up and recreate builder +docker buildx rm multiplatform-builder +docker buildx create --name multiplatform-builder --use +docker buildx inspect --bootstrap + +# Rebuild without cache +docker buildx build --no-cache \ + --platform linux/amd64,linux/arm64 \ + -t carloscastrotrejo/logicapps-dev:v1.0.0 \ + --push \ + . +``` + +### Can't Find Image Locally After Build +Multi-platform builds don't load locally by default. Pull the image: +```bash +docker pull carloscastrotrejo/logicapps-dev:v1.0.0 +``` + +### Build is Slow +- First build downloads base images for both platforms (8-10 minutes) +- Subsequent builds use cache (3-5 minutes) +- Check internet connection speed + +--- + +## 🎯 Best Practices + +### 1. Use Versioned Tags +```bash +./build-and-push.sh v2.0.0 # Good - traceable +./build-and-push.sh 1.131.9 # Good - matches Extension Bundle +./build-and-push.sh latest # Less traceable +``` + +### 2. Test Both Platforms +```bash +# Test amd64 +docker pull --platform linux/amd64 carloscastrotrejo/logicapps-dev:v2.0.0 +docker run --rm -it carloscastrotrejo/logicapps-dev:v2.0.0 func --version +docker run --rm -it carloscastrotrejo/logicapps-dev:v2.0.0 dotnet --version + +# Test arm64 +docker pull --platform linux/arm64 carloscastrotrejo/logicapps-dev:v2.0.0 +docker run --rm -it carloscastrotrejo/logicapps-dev:v2.0.0 func --version +docker run --rm -it carloscastrotrejo/logicapps-dev:v2.0.0 dotnet --version +``` + +### 3. Keep Builder Updated +Periodically recreate the builder: +```bash +docker buildx rm multiplatform-builder +docker buildx create --name multiplatform-builder --use +docker buildx inspect --bootstrap +``` + +--- + +## 📚 References + +- [Docker Buildx Documentation](https://docs.docker.com/buildx/working-with-buildx/) +- [Multi-platform Images Guide](https://docs.docker.com/build/building/multi-platform/) +- [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools) +- [Docker Hub Repository](https://hub.docker.com/r/carloscastrotrejo/logicapps-dev) \ No newline at end of file diff --git a/apps/vs-code-designer/src/assets/container/build-and-push.sh b/apps/vs-code-designer/src/assets/container/build-and-push.sh new file mode 100755 index 00000000000..dfbe64c35e8 --- /dev/null +++ b/apps/vs-code-designer/src/assets/container/build-and-push.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +# Build and Push Docker Image to Docker Hub +# This script builds the Logic Apps Standard Development container and pushes it to Docker Hub + +set -e + +# Configuration +DOCKER_USERNAME="carloscastrotrejo" +DOCKER_REPO="logicapps-dev" +DOCKER_IMAGE="${DOCKER_USERNAME}/${DOCKER_REPO}" + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Default version (can be overridden with command line argument) +VERSION="${1:-latest}" + +echo "==================================" +echo "Docker Image Build & Push Script" +echo "==================================" +echo "Repository: ${DOCKER_IMAGE}" +echo "Version: ${VERSION}" +echo "Build Context: ${SCRIPT_DIR}" +echo "==================================" +echo "" + +# Check if Docker is running +if ! docker info > /dev/null 2>&1; then + echo "❌ Error: Docker is not running. Please start Docker and try again." + exit 1 +fi + +# Check if logged into Docker Hub +echo "Checking Docker Hub authentication..." +if ! docker info | grep -q "Username: ${DOCKER_USERNAME}"; then + echo "⚠️ Not logged into Docker Hub. Attempting login..." + docker login + if [ $? -ne 0 ]; then + echo "❌ Docker Hub login failed. Please run 'docker login' manually." + exit 1 + fi +fi + +echo "✅ Docker Hub authentication verified" +echo "" + +# Setup buildx for multi-platform builds +echo "🔧 Setting up Docker buildx for multi-platform build..." +# Create a new builder instance if it doesn't exist +if ! docker buildx inspect multiplatform-builder > /dev/null 2>&1; then + echo "Creating new buildx builder 'multiplatform-builder'..." + docker buildx create --name multiplatform-builder --use +else + echo "Using existing buildx builder 'multiplatform-builder'..." + docker buildx use multiplatform-builder +fi + +# Bootstrap the builder (downloads necessary components) +docker buildx inspect --bootstrap + +echo "✅ Buildx ready for multi-platform build" +echo "" + +# Build and push multi-platform image +echo "🔨 Building multi-platform Docker image (linux/amd64, linux/arm64)..." +echo "Command: docker buildx build --platform linux/amd64,linux/arm64 -t ${DOCKER_IMAGE}:${VERSION} --push ${SCRIPT_DIR}" +echo "" +echo "⏳ This may take several minutes as it builds for multiple architectures..." +echo "" + +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t "${DOCKER_IMAGE}:${VERSION}" \ + --push \ + "${SCRIPT_DIR}" + +if [ $? -ne 0 ]; then + echo "❌ Docker buildx build failed!" + exit 1 +fi + +echo "" +echo "✅ Multi-platform image built and pushed successfully" +echo "" + +# Tag as latest if version is not "latest" +if [ "${VERSION}" != "latest" ]; then + echo "🏷️ Building and pushing 'latest' tag..." + docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t "${DOCKER_IMAGE}:latest" \ + --push \ + "${SCRIPT_DIR}" + echo "✅ Tagged and pushed as latest" + echo "" +fi + +if [ $? -ne 0 ]; then + echo "❌ Multi-platform build/push failed!" + exit 1 +fi + +echo "" +echo "==================================" +echo "✅ Successfully pushed to Docker Hub!" +echo "==================================" +echo "" +echo "Your multi-platform image is now available at:" +echo " - ${DOCKER_IMAGE}:${VERSION}" +if [ "${VERSION}" != "latest" ]; then + echo " - ${DOCKER_IMAGE}:latest" +fi +echo "" +echo "Supported platforms:" +echo " - linux/amd64 (Intel/AMD processors)" +echo " - linux/arm64 (Apple Silicon, ARM servers)" +echo "" +echo "Others can pull it with:" +echo " docker pull ${DOCKER_IMAGE}:${VERSION}" +if [ "${VERSION}" != "latest" ]; then + echo " docker pull ${DOCKER_IMAGE}:latest" +fi +echo "" +echo "Docker will automatically pull the correct architecture for their platform!" +echo "" +echo "To use in devcontainer.json, update the 'image' field:" +echo " \"image\": \"${DOCKER_IMAGE}:${VERSION}\"" +echo "==================================" diff --git a/apps/vs-code-designer/src/assets/container/devcontainer.json b/apps/vs-code-designer/src/assets/container/devcontainer.json new file mode 100644 index 00000000000..ffadbd3858f --- /dev/null +++ b/apps/vs-code-designer/src/assets/container/devcontainer.json @@ -0,0 +1,31 @@ +{ + "image": "carloscastrotrejo/logicapps-dev", + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-azurelogicapps", + "ms-azuretools.vscode-azurefunctions", + "ms-azuretools.vscode-docker", + "azurite.azurite", + "ms-azuretools.vscode-azureresourcegroups", + "ms-dotnettools.csharp", + "ms-dotnettools.csdevkit" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "files.exclude": { + "**/bin": true, + "**/obj": true + } + } + } + }, + "otherPortsAttributes": { + "onAutoForward": "silent" + }, + "portsAttributes": { + "*": { + "visibility": "public" + } + } +} \ No newline at end of file diff --git a/apps/vs-code-designer/src/constants.ts b/apps/vs-code-designer/src/constants.ts index 08372efc803..4eda7ca8efd 100644 --- a/apps/vs-code-designer/src/constants.ts +++ b/apps/vs-code-designer/src/constants.ts @@ -67,8 +67,6 @@ export const dotnetDependencyName = 'DotNetSDK'; // Node export const node = 'node'; -export const npm = 'npm'; -export const nodeJsDependencyName = 'NodeJs'; // Workflow export const workflowLocationKey = 'WORKFLOWS_LOCATION_NAME'; @@ -115,8 +113,8 @@ export const WorkflowKind = { export type WorkflowKind = (typeof WorkflowKind)[keyof typeof WorkflowKind]; // Designer -export const managementApiPrefix = '/runtime/webhooks/workflow/api/management'; -export const designerStartApi = '/runtime/webhooks/workflow/api/management/operationGroups'; +export const managementApiPrefix = 'runtime/webhooks/workflow/api/management'; +export const designerStartApi = 'runtime/webhooks/workflow/api/management/operationGroups'; export const designerApiLoadTimeout = 300000; // Commands @@ -186,9 +184,6 @@ export const extensionCommand = { startRemoteDebug: 'azureLogicAppsStandard.startRemoteDebug', validateLogicAppProjects: 'azureLogicAppsStandard.validateFunctionProjects', reportIssue: 'azureLogicAppsStandard.reportIssue', - validateAndInstallBinaries: 'azureLogicAppsStandard.validateAndInstallBinaries', - resetValidateAndInstallBinaries: 'azureLogicAppsStandard.resetValidateAndInstallBinaries', - disableValidateAndInstallBinaries: 'azureLogicAppsStandard.disableValidateAndInstallBinaries', azureAzuriteStart: 'azurite.start', parameterizeConnections: 'azureLogicAppsStandard.parameterizeConnections', loadDataMapFile: 'azureLogicAppsStandard.dataMap.loadDataMapFile', @@ -229,29 +224,19 @@ export const projectSubpathSetting = 'projectSubpath'; export const projectTemplateKeySetting = 'projectTemplateKey'; export const projectOpenBehaviorSetting = 'projectOpenBehavior'; export const stopFuncTaskPostDebugSetting = 'stopFuncTaskPostDebug'; -export const validateFuncCoreToolsSetting = 'validateFuncCoreTools'; -export const validateDotNetSDKSetting = 'validateDotNetSDK'; -export const validateNodeJsSetting = 'validateNodeJs'; export const showDeployConfirmationSetting = 'showDeployConfirmation'; export const deploySubpathSetting = 'deploySubpath'; export const preDeployTaskSetting = 'preDeployTask'; export const pickProcessTimeoutSetting = 'pickProcessTimeout'; -export const show64BitWarningSetting = 'show64BitWarning'; export const showProjectWarningSetting = 'showProjectWarning'; export const showTargetFrameworkWarningSetting = 'showTargetFrameworkWarning'; export const showStartDesignTimeMessageSetting = 'showStartDesignTimeMessage'; export const autoStartDesignTimeSetting = 'autoStartDesignTime'; -export const autoRuntimeDependenciesValidationAndInstallationSetting = 'autoRuntimeDependenciesValidationAndInstallation'; export const azuriteBinariesLocationSetting = 'azuriteLocationSetting'; export const driveLetterSMBSetting = 'driveLetterSMB'; export const parameterizeConnectionsInProjectLoadSetting = 'parameterizeConnectionsInProjectLoad'; export const showAutoStartAzuriteWarning = 'showAutoStartAzuriteWarning'; export const autoStartAzuriteSetting = 'autoStartAzurite'; -export const autoRuntimeDependenciesPathSettingKey = 'autoRuntimeDependenciesPath'; -export const dotNetBinaryPathSettingKey = 'dotnetBinaryPath'; -export const nodeJsBinaryPathSettingKey = 'nodeJsBinaryPath'; -export const funcCoreToolsBinaryPathSettingKey = 'funcCoreToolsBinaryPath'; -export const dependencyTimeoutSettingKey = 'dependencyTimeout'; export const unitTestExplorer = 'unitTestExplorer'; export const verifyConnectionKeysSetting = 'verifyConnectionKeys'; export const useSmbDeployment = 'useSmbDeploymentForHybrid'; @@ -270,18 +255,17 @@ export const ProjectDirectoryPathKey = 'ProjectDirectoryPath'; export const extensionVersionKey = 'FUNCTIONS_EXTENSION_VERSION'; export const azureStorageTypeSetting = 'Files'; export const isZipDeployEnabledSetting = 'IS_ZIP_DEPLOY_ENABLED'; + // Project -export const defaultVersionRange = '[1.*, 2.0.0)'; // Might need to be changed +export const EXTENSION_BUNDLE_VERSION = '1.131.9'; +export const defaultVersionRange = '[1.*, 2.0.0)'; export const funcWatchProblemMatcher = '$func-watch'; -export const extInstallCommand = 'extensions install'; -export const extInstallTaskName = `${func}: ${extInstallCommand}`; +export const extInstallTaskName = `${func}: extensions install`; export const tasksVersion = '2.0.0'; export const launchVersion = '0.2.0'; export const dotnetPublishTaskLabel = 'publish'; -export const defaultLogicAppsFolder = '.azurelogicapps'; export const defaultFunctionCoreToolsFolder = '.azure-functions-core-tools'; -export const defaultAzuritePathValue = path.join(os.homedir(), defaultLogicAppsFolder, '.azurite'); -export const defaultDependencyPathValue = path.join(os.homedir(), defaultLogicAppsFolder, 'dependencies'); +export const defaultAzuritePathValue = path.join(os.homedir(), '.azurite'); export const defaultExtensionBundlePathValue = path.join( os.homedir(), defaultFunctionCoreToolsFolder, @@ -291,14 +275,6 @@ export const defaultExtensionBundlePathValue = path.join( ); export const defaultDataMapperVersion = 2; -// Fallback Dependency Versions -export const DependencyVersion = { - dotnet6: '6.0.413', - funcCoreTools: '4.0.7030', - nodeJs: '18.17.1', -} as const; -export type DependencyVersion = (typeof DependencyVersion)[keyof typeof DependencyVersion]; - export const hostFileContent = { version: '2.0', extensionBundle: { @@ -314,12 +290,6 @@ export const hostFileContent = { }, }; -export const DependencyDefaultPath = { - dotnet: 'dotnet', - funcCoreTools: 'func', - node: 'node', -} as const; -export type DependencyDefaultPath = (typeof DependencyDefaultPath)[keyof typeof DependencyDefaultPath]; // .NET export const DotnetVersion = { net8: 'net8.0', @@ -332,12 +302,6 @@ export type DotnetVersion = (typeof DotnetVersion)[keyof typeof DotnetVersion]; export const dotnetExtensionId = 'ms-dotnettools.csharp'; -// Packages Manager -export const PackageManager = { - npm: 'npm', - brew: 'brew', -} as const; -export type PackageManager = (typeof PackageManager)[keyof typeof PackageManager]; // Operating System Platforms export const Platform = { windows: 'win32', diff --git a/apps/vs-code-designer/src/extensionVariables.ts b/apps/vs-code-designer/src/extensionVariables.ts index 1ff985d033f..bbbd9450d56 100644 --- a/apps/vs-code-designer/src/extensionVariables.ts +++ b/apps/vs-code-designer/src/extensionVariables.ts @@ -6,7 +6,6 @@ import type { VSCodeAzureSubscriptionProvider } from '@microsoft/vscode-azext-az import type DataMapperPanel from './app/commands/dataMapper/DataMapperPanel'; import type { AzureAccountTreeItemWithProjects } from './app/tree/AzureAccountTreeItemWithProjects'; import type { TestData } from './app/tree/unitTestTree'; -import { dotnet, func, node, npm } from './constants'; import type { ContainerApp, Site } from '@azure/arm-appservice'; import type { IActionContext, IAzExtOutputChannel } from '@microsoft/vscode-azext-utils'; import type { AzureHostExtensionApi } from '@microsoft/vscode-azext-utils/hostapi'; @@ -55,8 +54,6 @@ export namespace ext { export const prefix = 'azureLogicAppsStandard'; export const currentBundleVersion: Map = new Map(); export const pinnedBundleVersion: Map = new Map(); - export let defaultBundleVersion: string; - export let latestBundleVersion: string; // Services export let subscriptionProvider: VSCodeAzureSubscriptionProvider; @@ -75,16 +72,6 @@ export namespace ext { // Data Mapper panel export const dataMapPanelManagers: DataMapperPanelDictionary = {}; - // Functions - export const funcCliPath: string = func; - - // DotNet - export const dotNetCliPath: string = dotnet; - - // Node Js - export const nodeJsCliPath: string = node; - export const npmCliPath: string = npm; - // WebViews export const webViewKey = { designerLocal: 'designerLocal', diff --git a/apps/vs-code-designer/src/main.ts b/apps/vs-code-designer/src/main.ts index 4132382362b..2f25bcb083d 100644 --- a/apps/vs-code-designer/src/main.ts +++ b/apps/vs-code-designer/src/main.ts @@ -10,15 +10,13 @@ import { promptParameterizeConnections } from './app/commands/parameterizeConnec import { registerCommands } from './app/commands/registerCommands'; import { getResourceGroupsApi } from './app/resourcesExtension/getExtensionApi'; import type { AzureAccountTreeItemWithProjects } from './app/tree/AzureAccountTreeItemWithProjects'; -import { downloadExtensionBundle } from './app/utils/bundleFeed'; -import { stopAllDesignTimeApis } from './app/utils/codeless/startDesignTimeApi'; +import { promptStartDesignTimeOption, stopAllDesignTimeApis } from './app/utils/codeless/startDesignTimeApi'; import { UriHandler } from './app/utils/codeless/urihandler'; import { getExtensionVersion } from './app/utils/extension'; import { registerFuncHostTaskEvents } from './app/utils/funcCoreTools/funcHostTask'; import { verifyVSCodeConfigOnActivate } from './app/utils/vsCodeConfig/verifyVSCodeConfigOnActivate'; -import { extensionCommand, logicAppFilter } from './constants'; +import { autoStartDesignTimeSetting, extensionCommand, logicAppFilter, showStartDesignTimeMessageSetting } from './constants'; import { ext } from './extensionVariables'; -import { startOnboarding } from './onboarding'; import { registerAppServiceExtensionVariables } from '@microsoft/vscode-azext-azureappservice'; import { verifyLocalConnectionKeys } from './app/utils/appSettings/connectionKeys'; import { @@ -33,9 +31,10 @@ import { convertToWorkspace } from './app/commands/convertToWorkspace'; import TelemetryReporter from '@vscode/extension-telemetry'; import { getAllCustomCodeFunctionsProjects } from './app/utils/customCodeUtils'; import { createVSCodeAzureSubscriptionProvider } from './app/utils/services/VSCodeAzureSubscriptionProvider'; -import { logExtensionSettings, logSubscriptions } from './app/utils/telemetry'; +import { logExtensionSettings, logSubscriptions, runWithDurationTelemetry } from './app/utils/telemetry'; import { registerAzureUtilsExtensionVariables } from '@microsoft/vscode-azext-azureutils'; import { getAzExtResourceType, getAzureResourcesExtensionApi } from '@microsoft/vscode-azureresources-api'; +// import { tryReopenInDevContainer } from './app/utils/devContainer'; const perfStats = { loadStartTime: Date.now(), @@ -109,10 +108,16 @@ export async function activate(context: vscode.ExtensionContext) { await convertToWorkspace(activateContext); } - downloadExtensionBundle(activateContext); promptParameterizeConnections(activateContext, false); verifyLocalConnectionKeys(activateContext); - await startOnboarding(activateContext); + + await callWithTelemetryAndErrorHandling(autoStartDesignTimeSetting, async (actionContext: IActionContext) => { + await runWithDurationTelemetry(actionContext, showStartDesignTimeMessageSetting, async () => { + // TODO (ccastrotrejo): Need to revert validate to support container + // await validateTasksJson(actionContext, vscode.workspace.workspaceFolders); + await promptStartDesignTimeOption(activateContext); + }); + }); // Removed for unit test codefull experience standby //await prepareTestExplorer(context, activateContext); @@ -153,6 +158,9 @@ export async function activate(context: vscode.ExtensionContext) { logSubscriptions(activateContext); logExtensionSettings(activateContext); + + // Attempt to auto-reopen in dev container (centralized utility). Adds telemetry property attemptedDevContainerReopen. + // await tryReopenInDevContainer(activateContext); }); } diff --git a/apps/vs-code-designer/src/onboarding.ts b/apps/vs-code-designer/src/onboarding.ts deleted file mode 100644 index fea9929e37f..00000000000 --- a/apps/vs-code-designer/src/onboarding.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { validateAndInstallBinaries } from './app/commands/binaries/validateAndInstallBinaries'; -import { installBinaries, useBinariesDependencies } from './app/utils/binaries'; -import { promptStartDesignTimeOption } from './app/utils/codeless/startDesignTimeApi'; -import { runWithDurationTelemetry } from './app/utils/telemetry'; -import { validateTasksJson } from './app/utils/vsCodeConfig/tasks'; -import { - extensionCommand, - autoRuntimeDependenciesValidationAndInstallationSetting, - autoStartDesignTimeSetting, - showStartDesignTimeMessageSetting, -} from './constants'; -import { callWithTelemetryAndErrorHandling, type IActionContext } from '@microsoft/vscode-azext-utils'; -import * as vscode from 'vscode'; - -/** - * Prompts warning message for installing the installing/validate binaries and taks.json. - * @param {IActionContext} activateContext - Activation context. - */ -export const onboardBinaries = async (activateContext: IActionContext) => { - await callWithTelemetryAndErrorHandling(extensionCommand.validateAndInstallBinaries, async (actionContext: IActionContext) => { - await runWithDurationTelemetry(actionContext, extensionCommand.validateAndInstallBinaries, async () => { - const binariesInstallation = useBinariesDependencies(); - if (binariesInstallation) { - activateContext.telemetry.properties.lastStep = extensionCommand.validateAndInstallBinaries; - await validateAndInstallBinaries(actionContext); - await validateTasksJson(actionContext, vscode.workspace.workspaceFolders); - } - }); - }); -}; - -/** - * Start onboarding experience prompting inputs for user. - * This function will propmpt/install dependencies binaries, start design time api and start azurite. - * @param {IActionContext} activateContext - Activation context. - */ -export const startOnboarding = async (activateContext: IActionContext) => { - callWithTelemetryAndErrorHandling(autoRuntimeDependenciesValidationAndInstallationSetting, async (actionContext: IActionContext) => { - const binariesInstallStartTime = Date.now(); - await runWithDurationTelemetry(actionContext, autoRuntimeDependenciesValidationAndInstallationSetting, async () => { - activateContext.telemetry.properties.lastStep = autoRuntimeDependenciesValidationAndInstallationSetting; - await installBinaries(actionContext); - }); - activateContext.telemetry.measurements.binariesInstallDuration = Date.now() - binariesInstallStartTime; - }); - - await callWithTelemetryAndErrorHandling(autoStartDesignTimeSetting, async (actionContext: IActionContext) => { - await runWithDurationTelemetry(actionContext, showStartDesignTimeMessageSetting, async () => { - await promptStartDesignTimeOption(activateContext); - }); - }); -}; diff --git a/apps/vs-code-designer/src/package.json b/apps/vs-code-designer/src/package.json index d3853999b95..275a281da91 100644 --- a/apps/vs-code-designer/src/package.json +++ b/apps/vs-code-designer/src/package.json @@ -335,21 +335,6 @@ "title": "Report issue...", "category": "Azure Logic Apps" }, - { - "command": "azureLogicAppsStandard.validateAndInstallBinaries", - "title": "Validate and install dependency binaries", - "category": "Azure Logic Apps" - }, - { - "command": "azureLogicAppsStandard.resetValidateAndInstallBinaries", - "title": "Reset binaries dependency settings", - "category": "Azure Logic Apps" - }, - { - "command": "azureLogicAppsStandard.disableValidateAndInstallBinaries", - "title": "Disable binaries dependency settings", - "category": "Azure Logic Apps" - }, { "command": "azureLogicAppsStandard.dataMap.createNewDataMap", "title": "Create Data Map", @@ -800,9 +785,9 @@ "azureLogicAppsStandard.projectRuntime": { "scope": "resource", "type": "string", - "enum": ["~4", "~3"], + "enum": ["~4"], "description": "The default version of the Azure Functions runtime to use when performing operations like \"Create new logic app\".", - "enumDescriptions": ["Azure Functions v4", "Azure Functions v3 (.NET Core)"] + "enumDescriptions": ["Azure Functions v4"] }, "azureLogicAppsStandard.projectLanguage": { "scope": "resource", @@ -824,34 +809,11 @@ "type": "string", "description": "The default subpath for the workspace folder to use during deployment. If you set this value, you won't get a prompt for the folder path during deployment." }, - "azureLogicAppsStandard.dependencyTimeout": { - "type": "number", - "description": "The timeout (in seconds) to be used when validating and installing dependencies.", - "default": 300 - }, "azureLogicAppsStandard.autoRuntimeDependenciesPath": { "scope": "resource", "type": "string", "description": "The path for Azure Logic Apps extension runtime dependencies." }, - "azureLogicAppsStandard.dotnetBinaryPath": { - "scope": "resource", - "type": "string", - "description": "The path for Azure Logic Apps extension .NET SDK dependency binary.", - "default": "dotnet" - }, - "azureLogicAppsStandard.nodeJsBinaryPath": { - "scope": "resource", - "type": "string", - "description": "The path for Azure Logic Apps extension Node JS dependency binary.", - "default": "node" - }, - "azureLogicAppsStandard.funcCoreToolsBinaryPath": { - "scope": "resource", - "type": "string", - "description": "The path for Azure Logic Apps extension Azure Function Core Tools dependency binary.", - "default": "func" - }, "azureLogicAppsStandard.projectSubpath": { "scope": "resource", "type": "string", @@ -867,36 +829,11 @@ "description": "Automatically stop the task running the Azure Functions host when a debug sessions ends.", "default": true }, - "azureLogicAppsStandard.validateFuncCoreTools": { - "type": "boolean", - "description": "Make sure that Azure Functions Core Tools is installed before you start debugging.", - "default": true - }, - "azureLogicAppsStandard.validateDotNetSDK": { - "type": "boolean", - "description": "Make sure that the .NET SDK is installed before you start debugging.", - "default": true - }, - "azureLogicAppsStandard.validateNodeJs": { - "type": "boolean", - "description": "Make sure that Node JS is installed before you start debugging.", - "default": true - }, "azureLogicAppsStandard.showDeployConfirmation": { "type": "boolean", "description": "Ask to confirm before deploying to a function app in Azure. Deployment overwrites any previous deployment and can't be undone.", "default": true }, - "azureLogicAppsStandard.showNodeJsWarning": { - "type": "boolean", - "description": "Show a warning when your installed version of Node JS is outdated.", - "default": true - }, - "azureLogicAppsStandard.showMultiCoreToolsWarning": { - "type": "boolean", - "description": "Show a warning when multiple installations of the Azure Functions Core Tools are found.", - "default": true - }, "azureLogicAppsStandard.requestTimeout": { "type": "number", "description": "The timeout (in seconds) to be used when making requests, for example getting the latest templates.", @@ -922,16 +859,6 @@ "enum": ["AddToWorkspace", "OpenInNewWindow", "OpenInCurrentWindow"], "description": "The behavior to use after creating a new project. The options are \"AddToWorkspace\", \"OpenInNewWindow\", or \"OpenInCurrentWindow\"." }, - "azureLogicAppsStandard.show64BitWarning": { - "type": "boolean", - "description": "Show a warning to install a 64-bit version of the Azure Functions Core Tools when you create a .NET Framework project.", - "default": true - }, - "azureLogicAppsStandard.showDeploySubpathWarning": { - "type": "boolean", - "description": "Show a warning when the \"deploySubpath\" setting does not match the selected folder for deploying.", - "default": true - }, "azureLogicAppsStandard.showProjectWarning": { "type": "boolean", "description": "Show a warning when an Azure Logic App project was detected that has not been initialized for use in VS Code.", @@ -952,11 +879,6 @@ "description": "Start background design-time process at project load time.", "default": true }, - "azureLogicAppsStandard.autoRuntimeDependenciesValidationAndInstallation": { - "type": "boolean", - "description": "Enable automatic validation and installation for runtime dependencies at the configured path.", - "default": true - }, "azureLogicAppsStandard.showAutoStartAzuriteWarning": { "type": "boolean", "description": "Show a warning asking if user's would like to configure Azurite auto start.", diff --git a/apps/vs-code-designer/test-setup.ts b/apps/vs-code-designer/test-setup.ts index 8b7371e4fac..ad8ef85e1ec 100644 --- a/apps/vs-code-designer/test-setup.ts +++ b/apps/vs-code-designer/test-setup.ts @@ -100,6 +100,7 @@ vi.mock('vscode', () => ({ }, Uri: { file: (p: string) => ({ fsPath: p, toString: () => p }), + parse: vi.fn(), }, commands: { executeCommand: vi.fn(), @@ -117,8 +118,12 @@ vi.mock('vscode', () => ({ }, sessionId: 'test-session-id', appName: 'Visual Studio Code', + asExternalUri: vi.fn(), }, version: '1.85.0', + extensions: { + getExtension: vi.fn(), + }, })); vi.mock('./src/extensionVariables', () => ({ diff --git a/apps/vs-code-react/src/state/DesignerSlice.ts b/apps/vs-code-react/src/state/DesignerSlice.ts index 5b9c9a428ff..3d270d0c141 100644 --- a/apps/vs-code-react/src/state/DesignerSlice.ts +++ b/apps/vs-code-react/src/state/DesignerSlice.ts @@ -63,7 +63,6 @@ export const designerSlice = createSlice({ name: 'designer', initialState, reducers: { - /// TODO(ccastrotrejo): Update missing types initializeDesigner: (state, action: PayloadAction) => { const { panelMetadata, diff --git a/libs/designer-v2/src/lib/core/state/operation/operationSelector.ts b/libs/designer-v2/src/lib/core/state/operation/operationSelector.ts index 78d0c01d7ac..c8b8453dace 100644 --- a/libs/designer-v2/src/lib/core/state/operation/operationSelector.ts +++ b/libs/designer-v2/src/lib/core/state/operation/operationSelector.ts @@ -274,16 +274,18 @@ export const useBrandColor = (nodeId: string) => export const useIconUri = (nodeId: string) => useSelector(createSelector(getOperationState, (state) => getRecordEntry(state.operationMetadata, nodeId)?.iconUri ?? '')); -export const useAllIcons = () => - useSelector(createSelector(getOperationState, (state) => { - const icons: Record = {}; - Object.entries(state.operationMetadata).forEach(([nodeId, metadata]) => { - if (metadata?.iconUri) { - icons[nodeId] = metadata.iconUri; - } - }); - return icons; - })); +export const useAllIcons = () => + useSelector( + createSelector(getOperationState, (state) => { + const icons: Record = {}; + Object.entries(state.operationMetadata).forEach(([nodeId, metadata]) => { + if (metadata?.iconUri) { + icons[nodeId] = metadata.iconUri; + } + }); + return icons; + }) + ); export const useNodeConnectorId = (nodeId: string) => useSelector(createSelector(getOperationState, (state) => getRecordEntry(state.operationInfo, nodeId)?.connectorId)); diff --git a/libs/vscode-extension/src/lib/models/functions.ts b/libs/vscode-extension/src/lib/models/functions.ts index 312e8fb8e31..11bf8128556 100644 --- a/libs/vscode-extension/src/lib/models/functions.ts +++ b/libs/vscode-extension/src/lib/models/functions.ts @@ -4,23 +4,12 @@ import type { IWorkflowTemplate } from './templates'; import type { ISubscriptionContext } from '@microsoft/vscode-azext-utils'; export const FuncVersion = { - v1: '~1', - v2: '~2', - v3: '~3', v4: '~4', } as const; export type FuncVersion = (typeof FuncVersion)[keyof typeof FuncVersion]; export const latestGAVersion: FuncVersion = FuncVersion.v4; -export const azureFunctionsVersion = { - v1: 'Azure Functions v1', - v2: 'Azure Functions v2', - v3: 'Azure Functions v3', - v4: 'Azure Functions v4', -} as const; -export type azureFunctionsVersion = (typeof azureFunctionsVersion)[keyof typeof azureFunctionsVersion]; - export interface ICommandResult { code: number; cmdOutput: string; diff --git a/libs/vscode-extension/src/lib/models/host.ts b/libs/vscode-extension/src/lib/models/host.ts index 2ef59bb33b0..1047b297c62 100644 --- a/libs/vscode-extension/src/lib/models/host.ts +++ b/libs/vscode-extension/src/lib/models/host.ts @@ -34,12 +34,6 @@ export interface IBundleMetadata { version?: string; } -export interface IHostJsonV1 { - http?: { - routePrefix?: string; - }; -} - export interface IParsedHostJson { readonly routePrefix: string; readonly bundle?: IBundleMetadata; diff --git a/libs/vscode-extension/src/lib/models/project.ts b/libs/vscode-extension/src/lib/models/project.ts index 7c5f9310bec..307fd4bbb37 100644 --- a/libs/vscode-extension/src/lib/models/project.ts +++ b/libs/vscode-extension/src/lib/models/project.ts @@ -87,11 +87,8 @@ export interface IProjectWizardContext extends IActionContext { } export const OpenBehavior = { - addToWorkspace: 'AddToWorkspace', openInNewWindow: 'OpenInNewWindow', - openInCurrentWindow: 'OpenInCurrentWindow', alreadyOpen: 'AlreadyOpen', - dontOpen: 'DontOpen', } as const; export type OpenBehavior = (typeof OpenBehavior)[keyof typeof OpenBehavior]; diff --git a/libs/vscode-extension/src/lib/services/__test__/httpClient.spec.ts b/libs/vscode-extension/src/lib/services/__test__/httpClient.spec.ts index d95e6d1535d..cd73fb6017c 100644 --- a/libs/vscode-extension/src/lib/services/__test__/httpClient.spec.ts +++ b/libs/vscode-extension/src/lib/services/__test__/httpClient.spec.ts @@ -45,7 +45,6 @@ describe('HttpClient', () => { uri: '/test-get', url: `${baseUrl}/test-get`, headers: { - Authorization: '', 'x-ms-user-agent': 'LogicAppsDesigner/(host vscode 1.0.0)', }, }); @@ -190,7 +189,6 @@ describe('HttpClient', () => { url: `${baseUrl}/test-put`, content: { key: 'value' }, headers: { - Authorization: '', 'Content-Type': 'application/json', 'x-ms-user-agent': 'LogicAppsDesigner/(host vscode 1.0.0)', }, diff --git a/libs/vscode-extension/src/lib/services/httpClient.ts b/libs/vscode-extension/src/lib/services/httpClient.ts index ed59f91d026..d9f7374140a 100644 --- a/libs/vscode-extension/src/lib/services/httpClient.ts +++ b/libs/vscode-extension/src/lib/services/httpClient.ts @@ -32,7 +32,7 @@ export class HttpClient implements IHttpClient { headers: { ...this._extraHeaders, ...options.headers, - Authorization: `${isArmId ? this._accessToken : ''}`, + ...(isArmId ? { Authorization: `${this._accessToken}` } : {}), }, }; const response = await axios({ @@ -58,7 +58,7 @@ export class HttpClient implements IHttpClient { headers: { ...this._extraHeaders, ...options.headers, - Authorization: `${isArmId ? this._accessToken : ''}`, + ...(isArmId ? { Authorization: `${this._accessToken}` } : {}), 'Content-Type': 'application/json', }, data: options.content, @@ -115,7 +115,7 @@ export class HttpClient implements IHttpClient { headers: { ...this._extraHeaders, ...options.headers, - Authorization: `${isArmId ? this._accessToken : ''}`, + ...(isArmId ? { Authorization: `${this._accessToken}` } : {}), 'Content-Type': 'application/json', }, data: options.content,