diff --git a/package.json b/package.json index dc15be67a..3dad8df44 100644 --- a/package.json +++ b/package.json @@ -181,6 +181,12 @@ }, "enablement": "!virtualWorkspace" }, + { + "command": "azureFunctions.createNewProjectWithDockerfile", + "title": "%azureFunctions.createNewProjectWithDockerfile%", + "category": "Azure Functions", + "enablement": "!virtualWorkspace" + }, { "command": "azureFunctions.createSlot", "title": "%azureFunctions.createSlot%", @@ -381,6 +387,10 @@ "command": "azureFunctions.createNewProject", "group": "1_projects@2" }, + { + "command": "azureFunctions.createNewProjectWithDockerfile", + "group": "1_projects@3" + }, { "command": "azureFunctions.deploy", "group": "2_deploy@1" diff --git a/package.nls.json b/package.nls.json index 1359cdf7b..ed0f2e448 100644 --- a/package.nls.json +++ b/package.nls.json @@ -18,6 +18,7 @@ "azureFunctions.createFunctionAppAdvanced": "Create Function App in Azure... (Advanced)", "azureFunctions.createFunctionAppDetail": "For serverless, event driven apps and automation.", "azureFunctions.createNewProject": "Create New Project...", + "azureFunctions.createNewProjectWithDockerfile": "Create New Containerized Project...", "azureFunctions.createPythonVenv": "Create a virtual environment when creating a new Python project.", "azureFunctions.createSlot": "Create Slot...", "azureFunctions.createServiceConnector": "Create connection...", diff --git a/src/commands/createNewProject/createNewProject.ts b/src/commands/createNewProject/createNewProject.ts index 257e41461..fb001addc 100644 --- a/src/commands/createNewProject/createNewProject.ts +++ b/src/commands/createNewProject/createNewProject.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizard, type IActionContext } from '@microsoft/vscode-azext-utils'; +import { AzureWizard, UserCancelledError, type IActionContext } from '@microsoft/vscode-azext-utils'; import { window } from 'vscode'; import { latestGAVersion, tryParseFuncVersion } from '../../FuncVersion'; import { funcVersionSetting, projectLanguageSetting, projectOpenBehaviorSetting, projectTemplateKeySetting, type ProjectLanguage } from '../../constants'; import { ext } from '../../extensionVariables'; import { addLocalFuncTelemetry } from '../../funcCoreTools/getLocalFuncCoreToolsVersion'; import { tryGetLocalFuncVersion } from '../../funcCoreTools/tryGetLocalFuncVersion'; +import { validateFuncCoreToolsInstalled } from '../../funcCoreTools/validateFuncCoreToolsInstalled'; import { localize } from '../../localize'; import { getGlobalSetting, getWorkspaceSetting } from '../../vsCodeConfig/settings'; import type * as api from '../../vscode-azurefunctions.api'; @@ -18,6 +19,7 @@ import { FolderListStep } from './FolderListStep'; import { NewProjectLanguageStep } from './NewProjectLanguageStep'; import { OpenBehaviorStep } from './OpenBehaviorStep'; import { OpenFolderStep } from './OpenFolderStep'; +import { CreateDockerfileProjectStep } from './dockerfileSteps/CreateDockerfileProjectStep'; /** * @deprecated Use AzureFunctionsExtensionApi.createFunction instead @@ -54,6 +56,13 @@ export async function createNewProjectInternal(context: IActionContext, options: const wizardContext: Partial & IActionContext = Object.assign(context, options, { language, version: tryParseFuncVersion(version), projectTemplateKey }); const optionalExecuteStep = options.executeStep; + if (optionalExecuteStep instanceof CreateDockerfileProjectStep) { + const message: string = localize('installFuncTools', 'You must have the Azure Functions Core Tools installed to run this command.'); + if (!await validateFuncCoreToolsInstalled(context, message)) { + throw new UserCancelledError('validateFuncCoreToolsInstalled'); + } + } + if (options.folderPath) { FolderListStep.setProjectPath(wizardContext, options.folderPath); } @@ -70,6 +79,7 @@ export async function createNewProjectInternal(context: IActionContext, options: promptSteps: [new FolderListStep(), new NewProjectLanguageStep(options.templateId, options.functionSettings), new OpenBehaviorStep()], executeSteps: optionalExecuteStep ? [optionalExecuteStep, new OpenFolderStep()] : [new OpenFolderStep()] }); + await wizard.prompt(); await wizard.execute(); diff --git a/src/commands/createNewProject/dockerfileSteps/CreateDockerfileProjectStep.ts b/src/commands/createNewProject/dockerfileSteps/CreateDockerfileProjectStep.ts new file mode 100644 index 000000000..c7c334081 --- /dev/null +++ b/src/commands/createNewProject/dockerfileSteps/CreateDockerfileProjectStep.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { ext } from "@microsoft/vscode-azext-serviceconnector"; +import { AzureWizardExecuteStep, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; +import { cpUtils } from "../../../utils/cpUtils"; +import { type IFunctionWizardContext } from "../../createFunction/IFunctionWizardContext"; + +export class CreateDockerfileProjectStep extends AzureWizardExecuteStep{ + public priority: number = 100; + + public async execute(context: IFunctionWizardContext): Promise { + let language = nonNullValueAndProp(context, 'language').toLowerCase(); + if (language === 'c#') { + language = 'csharp'; + } + + await cpUtils.executeCommand(ext.outputChannel, nonNullValueAndProp(context, 'projectPath'), "func", "init", "--worker-runtime", language, "--docker"); + } + + public shouldExecute(): boolean { + return true; + } +} diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 57f400ae8..8c94b451a 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -27,7 +27,8 @@ import { copyFunctionUrl } from './copyFunctionUrl'; import { createChildNode } from './createChildNode'; import { createFunctionFromCommand } from './createFunction/createFunction'; import { createFunctionApp, createFunctionAppAdvanced } from './createFunctionApp/createFunctionApp'; -import { createNewProjectFromCommand } from './createNewProject/createNewProject'; +import { createNewProjectFromCommand, createNewProjectInternal } from './createNewProject/createNewProject'; +import { CreateDockerfileProjectStep } from './createNewProject/dockerfileSteps/CreateDockerfileProjectStep'; import { createSlot } from './createSlot'; import { deleteFunction } from './deleteFunction'; import { deleteFunctionApp } from './deleteFunctionApp'; @@ -75,6 +76,7 @@ export function registerCommands(): void { registerCommandWithTreeNodeUnwrapping('azureFunctions.createFunctionApp', createFunctionApp); registerCommandWithTreeNodeUnwrapping('azureFunctions.createFunctionAppAdvanced', createFunctionAppAdvanced); registerCommand('azureFunctions.createNewProject', createNewProjectFromCommand); + registerCommandWithTreeNodeUnwrapping('azureFunctions.createNewProjectWithDockerfile', async (context: IActionContext) => await createNewProjectInternal(context, { executeStep: new CreateDockerfileProjectStep(), languageFilter: /Python|C\#|(Java|Type)Script|PowerShell$/i })); registerCommandWithTreeNodeUnwrapping('azureFunctions.createSlot', createSlot); registerCommandWithTreeNodeUnwrapping('azureFunctions.deleteFunction', deleteFunction); registerCommandWithTreeNodeUnwrapping('azureFunctions.deleteFunctionApp', deleteFunctionApp); diff --git a/src/funcCoreTools/validateFuncCoreToolsInstalled.ts b/src/funcCoreTools/validateFuncCoreToolsInstalled.ts index b1f73a6a6..8b8acc195 100644 --- a/src/funcCoreTools/validateFuncCoreToolsInstalled.ts +++ b/src/funcCoreTools/validateFuncCoreToolsInstalled.ts @@ -18,7 +18,7 @@ import { getFuncCliPath, hasFuncCliSetting } from './getFuncCliPath'; import { getFuncPackageManagers } from './getFuncPackageManagers'; import { installFuncCoreTools, lastCoreToolsInstallCommand } from './installFuncCoreTools'; -export async function validateFuncCoreToolsInstalled(context: IActionContext, message: string, workspacePath: string): Promise { +export async function validateFuncCoreToolsInstalled(context: IActionContext, message: string, workspacePath?: string): Promise { let input: MessageItem | undefined; let installed: boolean = false; let failedInstall: string = localize('failedInstallFuncTools', 'Core Tools installation has failed and will have to be installed manually.');