diff --git a/src/commands/createNewProject/ProjectCreateStep/DotnetProjectCreateStep.ts b/src/commands/createNewProject/ProjectCreateStep/DotnetProjectCreateStep.ts index 01131232e..af1e8638a 100644 --- a/src/commands/createNewProject/ProjectCreateStep/DotnetProjectCreateStep.ts +++ b/src/commands/createNewProject/ProjectCreateStep/DotnetProjectCreateStep.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzExtFsExtra, DialogResponses, nonNullValueAndProp, type IActionContext } from '@microsoft/vscode-azext-utils'; +import { AzExtFsExtra, DialogResponses, nonNullValueAndProp, randomUtils, type IActionContext } from '@microsoft/vscode-azext-utils'; import { composeArgs, withArg, withNamedArg } from '@microsoft/vscode-processutils'; +import * as fs from 'fs'; +import * as os from 'os'; import * as path from 'path'; import { getMajorVersion, type FuncVersion } from '../../../FuncVersion'; import { ConnectionKey, ProjectLanguage, gitignoreFileName, hostFileName, localSettingsFileName } from '../../../constants'; @@ -35,18 +37,7 @@ export class DotnetProjectCreateStep extends ProjectCreateStepBase { const projName: string = projectName + language === ProjectLanguage.FSharp ? '.fsproj' : '.csproj'; const workerRuntime = nonNullProp(context, 'workerRuntime'); - // For containerized function apps we need to call func init before intialization as we want the .csproj file to be overwritten with the correct version - // currentely the version created by func init is behind the template version - if (context.containerizedProject) { - const runtime = context.workerRuntime?.capabilities.includes('isolated') ? 'dotnet-isolated' : 'dotnet'; - const args = composeArgs( - withArg('init'), - withNamedArg('--worker-runtime', runtime), - withNamedArg('--target-framework', runtime === 'dotnet' ? undefined : nonNullValueAndProp(context.workerRuntime, 'targetFramework')), // targetFramework is only supported for dotnet-isolated projects - withArg('--docker'), - )(); - await cpUtils.executeCommand(ext.outputChannel, context.projectPath, "func", args); - } else { + if (!context.containerizedProject) { await this.confirmOverwriteExisting(context, projName); } @@ -66,8 +57,40 @@ export class DotnetProjectCreateStep extends ProjectCreateStepBase { templateArgs.Framework = context.workerRuntime.targetFramework; } + // Create the project from the .NET template first so files are written into a clean directory await executeDotnetTemplateCreate(context, version, projTemplateKey, context.projectPath, identity, templateArgs); + // For containerized projects, generate the Dockerfile by running func init --docker in an + // isolated temp directory, then copy only the Dockerfile into the project. This avoids + // func init's internal dotnet new conflicting with files already created by the template. + if (context.containerizedProject) { + const runtime = context.workerRuntime?.capabilities.includes('isolated') ? 'dotnet-isolated' : 'dotnet'; + const tempDir = path.join(os.tmpdir(), `azfunc-docker-${randomUtils.getRandomHexString()}`); + try { + await fs.promises.mkdir(tempDir, { recursive: true }); + const args = composeArgs( + withArg('init'), + withNamedArg('--worker-runtime', runtime), + withNamedArg('--target-framework', runtime === 'dotnet' ? undefined : nonNullValueAndProp(context.workerRuntime, 'targetFramework')), + withArg('--docker'), + )(); + await cpUtils.executeCommand(ext.outputChannel, tempDir, "func", args); + + // Copy only the Dockerfile (and .dockerignore if present) into the project + for (const file of ['Dockerfile', '.dockerignore']) { + const src = path.join(tempDir, file); + const dest = path.join(context.projectPath, file); + try { + await fs.promises.copyFile(src, dest); + } catch { + // .dockerignore may not exist, that's fine + } + } + } finally { + await fs.promises.rm(tempDir, { recursive: true, force: true }).catch(() => { /* best-effort cleanup */ }); + } + } + await setLocalAppSetting(context, context.projectPath, ConnectionKey.Storage, '', MismatchBehavior.Overwrite); }