import * as fs from "fs" ; import * as path from "path" ; import { SimulateOptions } from "cordova-simulate" ; import * as vscode from "vscode" ; import { CordovaProjectHelper } from "./utils/cordovaProjectHelper" ; import { CordovaCommandHelper } from "./utils/cordovaCommandHelper" ; import * as semver from "semver" ; import { Telemetry } from "./utils/telemetry" ; import { TelemetryHelper } from "./utils/telemetryHelper" ; import { TsdHelper } from "./utils/tsdHelper" ; import { IonicCompletionProvider } from "./extension/completionProviders" ; import { CordovaSessionManager } from "./extension/cordovaSessionManager" ; import { ProjectsStorage } from "./extension/projectsStorage" ; import { PluginSimulator } from "./extension/simulate" ; import { CordovaDebugConfigProvider } from "./extension/debugConfigurationProvider" ; import { CordovaWorkspaceManager } from "./extension/cordovaWorkspaceManager" ; import customRequire from "./common/customRequire" ; import { findFileInFolderHierarchy } from "./utils/extensionHelper" ; import * as nls from "vscode-nls" ; nls . config ( { messageFormat : nls . MessageFormat . bundle , bundleFormat : nls . BundleFormat . standalone } ) ( ) ; const localize = nls . loadMessageBundle ( ) ; let PLUGIN_TYPE_DEFS_FILENAME = "pluginTypings.json" ; let PLUGIN_TYPE_DEFS_PATH = findFileInFolderHierarchy ( __dirname , PLUGIN_TYPE_DEFS_FILENAME ) ; let CORDOVA_TYPINGS_QUERYSTRING = "cordova" ; let JSCONFIG_FILENAME = "jsconfig.json" ; let TSCONFIG_FILENAME = "tsconfig.json" ; let EXTENSION_CONTEXT ; /**
 * We initialize the counter starting with a large value in order
 * to not overlap indices of the workspace folders originally generated by VS Code
 *  https let COUNT_WORKSPACE_FOLDERS = 9000 ; export function activate ( context ) { Telemetry . init ( "cordova-tools" , customRequire ( findFileInFolderHierarchy ( __dirname , "package.json" ) ) . version , { isExtensionProcess : true , projectRoot : "" } ) ; EXTENSION_CONTEXT = context ; let activateExtensionEvent = TelemetryHelper . createTelemetryActivity ( "activate" ) ; try { EXTENSION_CONTEXT . subscriptions . push ( vscode . workspace . onDidChangeWorkspaceFolders ( ( event ) => onChangeWorkspaceFolders ( event ) ) ) ; const configProvider = new CordovaDebugConfigProvider ( ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . debug . registerDebugConfigurationProvider ( "cordova" , configProvider ) ) ; const cordovaFactory = new CordovaSessionManager ( ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . debug . registerDebugAdapterDescriptorFactory ( "cordova" , cordovaFactory ) ) ; const workspaceFolders = vscode . workspace . workspaceFolders ; if ( workspaceFolders ) { registerCordovaCommands ( cordovaFactory ) ; workspaceFolders . forEach ( ( folder ) => { onFolderAdded ( folder ) ; } ) ; } activateExtensionEvent . properties [ "cordova.workspaceFoldersCount" ] = workspaceFolders . length ; Telemetry . send ( activateExtensionEvent ) ; } catch ( e ) { activateExtensionEvent . properties [ "cordova.error" ] = true ; Telemetry . send ( activateExtensionEvent ) ; throw e ; } } export function deactivate ( ) { console . log ( "Extension has been deactivated" ) ; } function onChangeWorkspaceFolders ( event ) { if ( event . removed . length ) { event . removed . forEach ( ( folder ) => { onFolderRemoved ( folder ) ; } ) ; } if ( event . added . length ) { event . added . forEach ( ( folder ) => { onFolderAdded ( folder ) ; } ) ; } } export function createAdditionalWorkspaceFolder ( folderPath ) { if ( fs . existsSync ( folderPath ) ) { return { uri : vscode . Uri . file ( folderPath ) , name : path . basename ( folderPath ) , index : COUNT_WORKSPACE_FOLDERS + 1 , } ; } else return null ; } export function onFolderAdded ( folder ) { let workspaceRoot = folder . uri . fsPath ; if ( ! CordovaProjectHelper . isCordovaProject ( workspaceRoot ) ) { vscode . window . showWarningMessage ( localize ( "ExtensionRequiresWorkspaceRootToBeCordovaProjectRoot" , "VS Code Cordova Tools extension requires the workspace root to be your Cordova project's root. The project '{0}' won't be available for debugging." , workspaceRoot ) ) ; return ; } let cordovaProjectTypeEvent = TelemetryHelper . createTelemetryEvent ( "cordova.projectType" ) ; TelemetryHelper . determineProjectTypes ( workspaceRoot ) . then ( ( projType ) => { cordovaProjectTypeEvent . properties [ "projectType" ] = projType ; } ) . finally ( ( ) => { Telemetry . send ( cordovaProjectTypeEvent ) ; } ) ; let watcher = vscode . workspace . createFileSystemWatcher ( "**/plugins/fetch.json" , false , false , false ) ; watcher . onDidChange ( ( ) => updatePluginTypeDefinitions ( workspaceRoot ) ) ; watcher . onDidDelete ( ( ) => updatePluginTypeDefinitions ( workspaceRoot ) ) ; watcher . onDidCreate ( ( ) => updatePluginTypeDefinitions ( workspaceRoot ) ) ; EXTENSION_CONTEXT . subscriptions . push ( watcher ) ; let simulator = new PluginSimulator ( ) ; let workspaceManager = new CordovaWorkspaceManager ( simulator , folder ) ; ProjectsStorage . addFolder ( folder , workspaceManager ) ; COUNT_WORKSPACE_FOLDERS ++ ; const ionicVersions = CordovaProjectHelper . checkIonicVersions ( workspaceRoot ) ; if ( ionicVersions . isIonic1 ) { EXTENSION_CONTEXT . subscriptions . push ( vscode . languages . registerCompletionItemProvider ( IonicCompletionProvider . JS_DOCUMENT_SELECTOR , new IonicCompletionProvider ( path . join ( findFileInFolderHierarchy ( __dirname , "snippets" ) , "ionicJs.json" ) ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . languages . registerCompletionItemProvider ( IonicCompletionProvider . HTML_DOCUMENT_SELECTOR , new IonicCompletionProvider ( path . join ( findFileInFolderHierarchy ( __dirname , "snippets" ) , "ionicHtml.json" ) ) ) ) ; } if ( CordovaProjectHelper . isIonicAngularProject ( workspaceRoot ) ) { let ionicTypings = [ path . join ( "jquery" , "jquery.d.ts" ) , path . join ( "cordova-ionic" , "plugins" , "keyboard.d.ts" ) , ] ; if ( ionicVersions . isIonic1 ) { ionicTypings = ionicTypings . concat ( [ path . join ( "angularjs" , "angular.d.ts" ) , path . join ( "ionic" , "ionic.d.ts" ) , ] ) ; } TsdHelper . installTypings ( CordovaProjectHelper . getOrCreateTypingsTargetPath ( workspaceRoot ) , ionicTypings , workspaceRoot ) ; } let pluginTypings = getPluginTypingsJson ( ) ; if ( ! pluginTypings ) { return ; } if ( ! ionicVersions . isIonic2 && ! ionicVersions . isIonic3 && ! ionicVersions . isIonic4 && ! ionicVersions . isIonic5 && ! CordovaProjectHelper . isTypescriptProject ( workspaceRoot ) ) { TsdHelper . installTypings ( CordovaProjectHelper . getOrCreateTypingsTargetPath ( workspaceRoot ) , [ pluginTypings [ CORDOVA_TYPINGS_QUERYSTRING ] . typingFile ] , workspaceRoot ) ; } updatePluginTypeDefinitions ( workspaceRoot ) ; let pluginFilePath = path . join ( workspaceRoot , ".vscode" , "plugins.json" ) ; if ( fs . existsSync ( pluginFilePath ) ) { fs . unlinkSync ( pluginFilePath ) ; } TelemetryHelper . sendPluginsList ( workspaceRoot , CordovaProjectHelper . getInstalledPlugins ( workspaceRoot ) ) ; let jsconfigPath = path . join ( workspaceRoot , JSCONFIG_FILENAME ) ; let tsconfigPath = path . join ( workspaceRoot , TSCONFIG_FILENAME ) ; Promise . all ( [ CordovaProjectHelper . exists ( jsconfigPath ) , CordovaProjectHelper . exists ( tsconfigPath ) ] ) . then ( ( [ jsExists , tsExists ] ) => { if ( ! jsExists && ! tsExists ) { fs . promises . writeFile ( jsconfigPath , "{}" ) . then ( ( ) => { vscode . window . showInformationMessage ( "A 'jsconfig.json' file was created to enable IntelliSense. You may need to reload your open JS file(s)." ) ; } ) ; } } ) ; } function onFolderRemoved ( folder ) { Object . keys ( ProjectsStorage . projectsCache ) . forEach ( path => { if ( CordovaProjectHelper . checkPathBelongsToHierarchy ( folder . uri . fsPath . toLowerCase ( ) , path ) ) { ProjectsStorage . delFolder ( path ) ; } } ) ; } function getPluginTypingsJson ( ) { if ( CordovaProjectHelper . existsSync ( PLUGIN_TYPE_DEFS_PATH ) ) { return customRequire ( PLUGIN_TYPE_DEFS_PATH ) ; } console . error ( localize ( "CordovaPluginTypeDeclarationMappingFileIsMissing" , "Cordova plugin type declaration mapping file 'pluginTypings.json' is missing from the extension folder." ) ) ; return null ; } function getNewTypeDefinitions ( installedPlugins ) { let pluginTypings = getPluginTypingsJson ( ) ; if ( ! pluginTypings ) { return ; } return installedPlugins . filter ( pluginName => ! ! pluginTypings [ pluginName ] ) . map ( pluginName => pluginTypings [ pluginName ] . typingFile ) ; } function addPluginTypeDefinitions ( projectRoot , installedPlugins , currentTypeDefs ) { let pluginTypings = getPluginTypingsJson ( ) ; if ( ! pluginTypings ) { return ; } let typingsToAdd = installedPlugins . filter ( ( pluginName ) => { if ( pluginTypings [ pluginName ] ) { return currentTypeDefs . indexOf ( pluginTypings [ pluginName ] . typingFile ) < 0 ; } let unknownPluginEvent = TelemetryHelper . createTelemetryEvent ( "unknownPlugin" ) ; unknownPluginEvent . setPiiProperty ( "plugin" , pluginName ) ; Telemetry . send ( unknownPluginEvent ) ; return false ; } ) . map ( ( pluginName ) => { return pluginTypings [ pluginName ] . typingFile ; } ) ; TsdHelper . installTypings ( CordovaProjectHelper . getOrCreateTypingsTargetPath ( projectRoot ) , typingsToAdd , projectRoot ) ; } function removePluginTypeDefinitions ( projectRoot , currentTypeDefs , newTypeDefs ) { let typeDefsToRemove = currentTypeDefs . filter ( ( typeDef ) => newTypeDefs . indexOf ( typeDef ) < 0 ) ; TsdHelper . removeTypings ( CordovaProjectHelper . getOrCreateTypingsTargetPath ( projectRoot ) , typeDefsToRemove , projectRoot ) ; } function getRelativeTypeDefinitionFilePath ( projectRoot , parentPath , typeDefinitionFile ) { return path . relative ( CordovaProjectHelper . getOrCreateTypingsTargetPath ( projectRoot ) , path . resolve ( parentPath , typeDefinitionFile ) ) . replace ( /\\/g , "\/" ) ; } function updatePluginTypeDefinitions ( cordovaProjectRoot ) { const ionicVersions = CordovaProjectHelper . checkIonicVersions ( cordovaProjectRoot ) ; if ( ionicVersions . isIonic2 || ionicVersions . isIonic3 || ionicVersions . isIonic4 || ionicVersions . isIonic5 || CordovaProjectHelper . isTypescriptProject ( cordovaProjectRoot ) ) { return ; } let installedPlugins = CordovaProjectHelper . getInstalledPlugins ( cordovaProjectRoot ) ; const nodeModulesDir = path . resolve ( cordovaProjectRoot , "node_modules" ) ; if ( semver . gte ( vscode . version , "1.7.2-insider" ) && fs . existsSync ( nodeModulesDir ) ) { let installedNpmModules = [ ] ; try { installedNpmModules = fs . readdirSync ( nodeModulesDir ) ; } catch ( e ) { } const pluginTypingsJson = getPluginTypingsJson ( ) || { } ; installedPlugins = installedPlugins . filter ( pluginId => { const forceInstallTypings = pluginTypingsJson [ pluginId ] && pluginTypingsJson [ pluginId ] . forceInstallTypings ; return forceInstallTypings || installedNpmModules . indexOf ( pluginId ) === - 1 ; } ) ; } let newTypeDefs = getNewTypeDefinitions ( installedPlugins ) ; let cordovaPluginTypesFolder = CordovaProjectHelper . getCordovaPluginTypeDefsPath ( cordovaProjectRoot ) ; let ionicPluginTypesFolder = CordovaProjectHelper . getIonicPluginTypeDefsPath ( cordovaProjectRoot ) ; if ( ! CordovaProjectHelper . existsSync ( cordovaPluginTypesFolder ) ) { addPluginTypeDefinitions ( cordovaProjectRoot , installedPlugins , [ ] ) ; return ; } let currentTypeDefs = [ ] ; fs . readdir ( cordovaPluginTypesFolder , ( err , cordovaTypeDefs ) => { if ( err ) { } if ( cordovaTypeDefs ) { currentTypeDefs = cordovaTypeDefs . map ( typeDef => getRelativeTypeDefinitionFilePath ( cordovaProjectRoot , cordovaPluginTypesFolder , typeDef ) ) ; } fs . readdir ( ionicPluginTypesFolder , ( err , ionicTypeDefs ) => { if ( err ) { } if ( ionicTypeDefs ) { currentTypeDefs . concat ( ionicTypeDefs . map ( typeDef => getRelativeTypeDefinitionFilePath ( cordovaProjectRoot , ionicPluginTypesFolder , typeDef ) ) ) ; } addPluginTypeDefinitions ( cordovaProjectRoot , installedPlugins , currentTypeDefs ) ; removePluginTypeDefinitions ( cordovaProjectRoot , currentTypeDefs , newTypeDefs ) ; } ) ; } ) ; } function launchSimulateCommand ( cordovaProjectRoot , options ) { return TelemetryHelper . generate ( "simulateCommand" , ( generator ) => { return TelemetryHelper . determineProjectTypes ( cordovaProjectRoot ) . then ( ( projectType ) => { generator . add ( "simulateOptions" , { platform : options . platform , target : options . target , livereload : options . livereload , forceprepare : options . forceprepare , corsproxy : options . corsproxy , } , false ) ; generator . add ( "projectType" , projectType , false ) ; generator . add ( "visibleTextEditorsCount" , vscode . window . visibleTextEditors . length , false ) ; return projectType ; } ) ; } ) . then ( ( projectType ) => { const uri = vscode . Uri . file ( cordovaProjectRoot ) ; const workspaceFolder = < vscode . WorkspaceFolder > vscode . workspace . getWorkspaceFolder ( uri ) ; return ProjectsStorage . getFolder ( workspaceFolder ) . pluginSimulator . simulate ( cordovaProjectRoot , options , projectType ) ; } ) ; } function registerCordovaCommands ( cordovaSessionManager ) { EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "cordova.restart" , ( ) => commandWrapper ( CordovaCommandHelper . restartCordovaDebugging , [ cordovaSessionManager ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "cordova.prepare" , ( ) => commandWrapper ( CordovaCommandHelper . executeCordovaCommand , [ "prepare" ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "cordova.build" , ( ) => commandWrapper ( CordovaCommandHelper . executeCordovaCommand , [ "build" ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "cordova.run" , ( ) => commandWrapper ( CordovaCommandHelper . executeCordovaCommand , [ "run" ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "ionic.prepare" , ( ) => commandWrapper ( CordovaCommandHelper . executeCordovaCommand , [ "prepare" , true ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "ionic.build" , ( ) => commandWrapper ( CordovaCommandHelper . executeCordovaCommand , [ "build" , true ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "ionic.run" , ( ) => commandWrapper ( CordovaCommandHelper . executeCordovaCommand , [ "run" , true ] ) ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "cordova.simulate.android" , ( ) => { return selectProject ( ) . then ( ( project ) => { return launchSimulateCommand ( project . workspaceRoot . uri . fsPath , { dir : project . workspaceRoot . uri . fsPath , target : "chrome" , platform : "android" , lang : vscode . env . language } ) ; } ) ; } ) ) ; EXTENSION_CONTEXT . subscriptions . push ( vscode . commands . registerCommand ( "cordova.simulate.ios" , ( ) => { return selectProject ( ) . then ( ( project ) => { return launchSimulateCommand ( project . workspaceRoot . uri . fsPath , { dir : project . workspaceRoot . uri . fsPath , target : "chrome" , platform : "ios" , lang : vscode . env . language } ) ; } ) ; } ) ) ; } function selectProject ( ) { let keys = Object . keys ( ProjectsStorage . projectsCache ) ; if ( keys . length > 1 ) { return new Promise ( ( resolve , reject ) => { vscode . window . showQuickPick ( keys ) . then ( ( selected ) => { if ( selected ) { resolve ( ProjectsStorage . projectsCache [ selected ] ) ; } } , reject ) ; } ) ; } else if ( keys . length === 1 ) { return Promise . resolve ( ProjectsStorage . projectsCache [ keys [ 0 ] ] ) ; } else { return Promise . reject ( new Error ( localize ( "NoCordovaProjectIsFound" , "No Cordova project is found" ) ) ) ; } } function commandWrapper ( fn , args ) { return selectProject ( ) . then ( ( project ) => { return fn ( project . workspaceRoot . uri . fsPath , ... args ) ; } ) ; }