import * as fse from 'fs-extra' ; import * as path from 'path' ; import { DebugConfiguration , MessageItem , TaskDefinition , WorkspaceFolder } from 'vscode' ; import { AzureWizardExecuteStep , IActionContext } from 'vscode-azureextensionui' ; import { deploySubpathSetting , func , funcVersionSetting , gitignoreFileName , launchFileName , preDeployTaskSetting , ProjectLanguage , projectLanguageSetting , projectSubpathSetting , settingsFileName , tasksFileName } from '../../../constants' ; import { ext } from '../../../extensionVariables' ; import { FuncVersion } from '../../../FuncVersion' ; import { localize } from '../../../localize' ; import { confirmEditJsonFile , isPathEqual , isSubpath } from '../../../utils/fs' ; import { nonNullProp } from '../../../utils/nonNull' ; import { isMultiRootWorkspace } from '../../../utils/workspace' ; import { IExtensionsJson } from '../../../vsCodeConfig/extensions' ; import { getDebugConfigs , getLaunchVersion , ILaunchJson , isDebugConfigEqual , launchVersion , updateDebugConfigs , updateLaunchVersion } from '../../../vsCodeConfig/launch' ; import { updateWorkspaceSetting } from '../../../vsCodeConfig/settings' ; import { getTasks , getTasksVersion , ITask , ITasksJson , tasksVersion , updateTasks , updateTasksVersion } from '../../../vsCodeConfig/tasks' ; import { IProjectWizardContext } from '../../createNewProject/IProjectWizardContext' ; export abstract class InitVSCodeStepBase extends AzureWizardExecuteStep < IProjectWizardContext > { public priority : number = 20 ; protected preDeployTask ? : string ; protected settings : ISettingToAdd [ ] = [ ] ; public async execute ( context ) { await this . executeCore ( context ) ; const version = nonNullProp ( context , 'version' ) ; context . telemetry . properties . projectRuntime = version ; const language = nonNullProp ( context , 'language' ) ; context . telemetry . properties . projectLanguage = language ; context . telemetry . properties . isProjectInSubDir = String ( isSubpath ( context . workspacePath , context . projectPath ) ) ; const vscodePath = path . join ( context . workspacePath , '.vscode' ) ; await fse . ensureDir ( vscodePath ) ; await this . writeTasksJson ( context , vscodePath , language ) ; await this . writeLaunchJson ( context , context . workspaceFolder , vscodePath , version ) ; await this . writeSettingsJson ( context , vscodePath , language , version ) ; await this . writeExtensionsJson ( context , vscodePath , language ) ; const gitignorePath = path . join ( context . workspacePath , gitignoreFileName ) ; if ( await fse . pathExists ( gitignorePath ) ) { let gitignoreContents = ( await fse . readFile ( gitignorePath ) ) . toString ( ) ; gitignoreContents = gitignoreContents . replace ( /^\.vscode(\/|\\)?\s*$/gm , '' ) ; await fse . writeFile ( gitignorePath , gitignoreContents ) ; } } public shouldExecute ( _context ) { return true ; } protected abstract executeCore ( context ) ; protected abstract getTasks ( language ) ; protected getDebugConfiguration ? ( version ) ; protected getRecommendedExtensions ? ( language ) ; protected setDeploySubpath ( context , deploySubpath ) { deploySubpath = this . addSubDir ( context , deploySubpath ) ; this . settings . push ( { key : deploySubpathSetting , value : deploySubpath } ) ; return deploySubpath ; } protected addSubDir ( context , fsPath ) { const subDir = path . relative ( context . workspacePath , context . projectPath ) ; return path . posix . join ( subDir , fsPath ) ; } private async writeTasksJson ( context , vscodePath , language ) { const newTasks = this . getTasks ( language ) ; for ( const task of newTasks ) { let cwd = ( task . options && task . options . cwd ) || '.' ; cwd = this . addSubDir ( context , cwd ) ; if ( ! isPathEqual ( cwd , '.' ) ) { task . options = task . options || { } ; task . options . cwd = path . posix . join ( '${workspaceFolder}' , cwd ) ; } } const versionMismatchError = new Error ( localize ( 'versionMismatchError' , 'The version in your {0} must be "{1}" to work with Azure Functions.' , tasksFileName , tasksVersion ) ) ; if ( context . workspaceFolder && ! isMultiRootWorkspace ( ) ) { const currentVersion = getTasksVersion ( context . workspaceFolder ) ; if ( ! currentVersion ) { await updateTasksVersion ( context . workspaceFolder , tasksVersion ) ; } else if ( currentVersion !== tasksVersion ) { throw versionMismatchError ; } await updateTasks ( context . workspaceFolder , await this . insertNewTasks ( context , getTasks ( context . workspaceFolder ) , newTasks ) ) ; } else { const tasksJsonPath = path . join ( vscodePath , tasksFileName ) ; await confirmEditJsonFile ( context , tasksJsonPath , async ( data ) : Promise < ITasksJson > => { if ( ! data . version ) { data . version = tasksVersion ; } else if ( data . version !== tasksVersion ) { throw versionMismatchError ; } data . tasks = await this . insertNewTasks ( context , data . tasks , newTasks ) ; return data ; } ) ; } } private async insertNewTasks ( context , existingTasks , newTasks ) { existingTasks = existingTasks || [ ] ; newTasks = newTasks . filter ( t1 => { const t1String = JSON . stringify ( t1 ) ; return ! existingTasks ?. some ( t2 => t1String === JSON . stringify ( t2 ) ) ; } ) ; const nonMatchingTasks = [ ] ; const matchingTaskLabels = [ ] ; for ( const existingTask of existingTasks ) { const existingLabel = this . getTaskLabel ( existingTask ) ; if ( existingLabel && newTasks . some ( newTask => existingLabel === this . getTaskLabel ( newTask ) ) ) { matchingTaskLabels . push ( existingLabel ) ; } else { nonMatchingTasks . push ( existingTask ) ; } } if ( matchingTaskLabels . length > 0 ) { const message = localize ( 'confirmOverwriteTasks' , 'This will overwrite the following tasks in your tasks.json: "{0}"' , matchingTaskLabels . join ( '", "' ) ) ; const overwrite = { title : localize ( 'overwrite' , 'Overwrite' ) } ; await context . ui . showWarningMessage ( message , { modal : true , stepName : 'confirmOverwriteTasks' } , overwrite ) ; } return nonMatchingTasks . concat ( ... newTasks ) ; } private getTaskLabel ( task ) { switch ( task . type ) { case func : return `${ task . type }: ${ task . command }` ; case 'shell' : case 'process' : return task . label ; default : return undefined ; } } private async writeLaunchJson ( context , folder , vscodePath , version ) { if ( this . getDebugConfiguration ) { const newDebugConfig = this . getDebugConfiguration ( version ) ; const versionMismatchError = new Error ( localize ( 'versionMismatchError' , 'The version in your {0} must be "{1}" to work with Azure Functions.' , launchFileName , launchVersion ) ) ; if ( folder && ! isMultiRootWorkspace ( ) ) { const currentVersion = getLaunchVersion ( folder ) ; if ( ! currentVersion ) { await updateLaunchVersion ( folder , launchVersion ) ; } else if ( currentVersion !== launchVersion ) { throw versionMismatchError ; } await updateDebugConfigs ( folder , this . insertLaunchConfig ( getDebugConfigs ( folder ) , newDebugConfig ) ) ; } else { const launchJsonPath = path . join ( vscodePath , launchFileName ) ; await confirmEditJsonFile ( context , launchJsonPath , ( data ) : ILaunchJson => { if ( ! data . version ) { data . version = launchVersion ; } else if ( data . version !== launchVersion ) { throw versionMismatchError ; } data . configurations = this . insertLaunchConfig ( data . configurations , newDebugConfig ) ; return data ; } ) ; } } } private insertLaunchConfig ( existingConfigs , newConfig ) { existingConfigs = existingConfigs || [ ] ; existingConfigs = existingConfigs . filter ( l1 => ! isDebugConfigEqual ( l1 , newConfig ) ) ; existingConfigs . push ( newConfig ) ; return existingConfigs ; } private async writeSettingsJson ( context , vscodePath , language , version ) { const settings = this . settings . concat ( { key : projectLanguageSetting , value : language } , { key : funcVersionSetting , value : version } , { prefix : 'debug' , key : 'internalConsoleOptions' , value : 'neverOpen' } ) ; if ( path . posix . relative ( context . projectPath , context . workspacePath ) . startsWith ( '../..' ) ) { settings . push ( { key : projectSubpathSetting , value : path . posix . relative ( context . workspacePath , context . projectPath ) } ) ; } if ( this . preDeployTask ) { settings . push ( { key : preDeployTaskSetting , value : this . preDeployTask } ) ; } if ( context . workspaceFolder ) { for ( const setting of settings ) { await updateWorkspaceSetting ( setting . key , setting . value , context . workspacePath , setting . prefix ) ; } } else { const settingsJsonPath = path . join ( vscodePath , settingsFileName ) ; await confirmEditJsonFile ( context , settingsJsonPath , ( data ) : { } => { for ( const setting of settings ) { const key = `${ setting . prefix || ext . prefix }.${ setting . key }` ; data [ key ] = setting . value ; } return data ; } ) ; } } private async writeExtensionsJson ( context , vscodePath , language ) { const extensionsJsonPath = path . join ( vscodePath , 'extensions.json' ) ; await confirmEditJsonFile ( context , extensionsJsonPath , ( data ) : { } => { const recommendations = [ 'ms-azuretools.vscode-azurefunctions' ] ; if ( this . getRecommendedExtensions ) { recommendations . push ( ... this . getRecommendedExtensions ( language ) ) ; } if ( data . recommendations ) { recommendations . push ( ... data . recommendations ) ; } data . recommendations = recommendations . filter ( ( rec , index ) => recommendations . indexOf ( rec ) === index ) ; return data ; } ) ; } } interface ISettingToAdd { key : string ; value : string | { } ; prefix ? : string ; }