import * as fs from "fs-plus" ; import * as path from "path" ; import * as vscode from "vscode" ; import { IoTCubeCommands } from "../common/Commands" ; import { ArgumentEmptyOrNullError } from "../common/Error/OperationFailedErrors/ArgumentEmptyOrNullError" ; import { WorkspaceConfigNotFoundError } from "../common/Error/SystemErrors/WorkspaceConfigNotFoundError" ; import { TypeNotSupportedError } from "../common/Error/SystemErrors/TypeNotSupportedError" ; import { ConfigHandler } from "../configHandler" ; import { ConfigKey , EventNames , FileNames , ScaffoldType } from "../constants" ; import { FileUtility } from "../FileUtility" ; import { TelemetryContext , TelemetryWorker } from "../telemetry" ; import { getWorkspaceFile , updateProjectHostTypeConfig } from "../utils" ; import { AzureComponentConfig } from "./AzureComponentConfig" ; import { Component , ComponentType } from "./Interfaces/Component" ; import { ProjectHostType } from "./Interfaces/ProjectHostType" ; import { ProjectTemplateType , TemplateFileInfo } from "./Interfaces/ProjectTemplate" ; import { Workspace } from "./Interfaces/Workspace" ; import { IoTWorkbenchProjectBase , OpenScenario } from "./IoTWorkbenchProjectBase" ; import { DirectoryNotFoundError } from "../common/Error/OperationFailedErrors/DirectoryNotFoundError" ; import { FileNotFoundError } from "../common/Error/OperationFailedErrors/FileNotFound" ; const importLazy = require ( "import-lazy" ) ; const az3166DeviceModule = importLazy ( ( ) => require ( "./AZ3166Device" ) ) ( ) ; const azureComponentConfigModule = importLazy ( ( ) => require ( "./AzureComponentConfig" ) ) ( ) ; const azureFunctionsModule = importLazy ( ( ) => require ( "./AzureFunctions" ) ) ( ) ; const esp32DeviceModule = importLazy ( ( ) => require ( "./Esp32Device" ) ) ( ) ; const ioTHubModule = importLazy ( ( ) => require ( "./IoTHub" ) ) ( ) ; const ioTHubDeviceModule = importLazy ( ( ) => require ( "./IoTHubDevice" ) ) ( ) ; export class IoTWorkspaceProject extends IoTWorkbenchProjectBase { private deviceRootPath = "" ; private workspaceConfigFilePath = "" ; static folderName = { deviceDefaultFolderName : "Device" , functionDefaultFolderName : "Functions" } ; constructor ( context , channel , telemetryContext , rootFolderPath ) { super ( context , channel , telemetryContext ) ; this . projectHostType = ProjectHostType . Workspace ; if ( ! rootFolderPath ) { throw new ArgumentEmptyOrNullError ( "construct iot workspace project" , "root folder path" , "Please initialize iot workspace project with root folder path." ) ; } this . projectRootPath = rootFolderPath ; this . telemetryContext . properties . projectHostType = this . projectHostType ; } async load ( scaffoldType , initLoad = false ) { await this . validateProjectRootPathExists ( "load project" , scaffoldType ) ; const devicePath = ConfigHandler . get < string > ( ConfigKey . devicePath ) ; if ( ! devicePath ) { throw new WorkspaceConfigNotFoundError ( ConfigKey . devicePath ) ; } this . deviceRootPath = path . join ( this . projectRootPath , devicePath ) ; if ( ! ( await FileUtility . directoryExists ( scaffoldType , this . deviceRootPath ) ) ) { throw new DirectoryNotFoundError ( "load iot workspace project" , `device root path ${ this . deviceRootPath }` , "Please initialize the project first." ) ; } this . iotWorkbenchProjectFilePath = path . join ( this . deviceRootPath , FileNames . iotWorkbenchProjectFileName ) ; await updateProjectHostTypeConfig ( scaffoldType , this . iotWorkbenchProjectFilePath , this . projectHostType ) ; await this . loadWorkspaceConfigFilePath ( scaffoldType ) ; if ( initLoad ) { this . sendLoadEventTelemetry ( this . extensionContext ) ; } const boardId = ConfigHandler . get < string > ( ConfigKey . boardId ) ; if ( ! boardId ) { throw new WorkspaceConfigNotFoundError ( ConfigKey . boardId ) ; } this . initDeviceComponents ( boardId , this . deviceRootPath ) ; await Promise . all ( this . componentList . map ( async ( component ) => { await component . load ( ) ; } ) ) ; await this . loadAzureConfigAndComponents ( scaffoldType ) ; await Promise . all ( this . componentList . map ( async ( component ) => { await component . checkPrerequisites ( "load project" ) ; } ) ) ; } async create ( templateFilesInfo , projectType , boardId , openInNewWindow ) { const createTimeScaffoldType = ScaffoldType . Local ; const workspace = { folders : [ ] , settings : { } } ; this . deviceRootPath = path . join ( this . projectRootPath , IoTWorkspaceProject . folderName . deviceDefaultFolderName ) ; this . iotWorkbenchProjectFilePath = path . join ( this . deviceRootPath , FileNames . iotWorkbenchProjectFileName ) ; this . initDeviceComponents ( boardId , this . deviceRootPath , templateFilesInfo ) ; this . initAzureComponents ( projectType , workspace ) ; workspace . folders . push ( { path : IoTWorkspaceProject . folderName . deviceDefaultFolderName } ) ; workspace . settings [ `IoTWorkbench.${ ConfigKey . boardId }` ] = boardId ; workspace . settings [ `IoTWorkbench.${ ConfigKey . devicePath }` ] = IoTWorkspaceProject . folderName . deviceDefaultFolderName ; await Promise . all ( this . componentList . map ( async ( component ) => { await component . checkPrerequisites ( "create project" ) ; } ) ) ; const azureConfigFileHandler = new azureComponentConfigModule . AzureConfigFileHandler ( this . projectRootPath ) ; await azureConfigFileHandler . createIfNotExists ( createTimeScaffoldType ) ; await updateProjectHostTypeConfig ( createTimeScaffoldType , this . iotWorkbenchProjectFilePath , this . projectHostType ) ; this . workspaceConfigFilePath = path . join ( this . projectRootPath , `${ path . basename ( this . projectRootPath ) }${ FileNames . workspaceExtensionName }` ) ; await FileUtility . writeJsonFile ( createTimeScaffoldType , this . workspaceConfigFilePath , workspace ) ; try { await Promise . all ( this . componentList . map ( async ( component ) => { await component . create ( ) ; } ) ) ; } catch ( error ) { fs . removeSync ( this . projectRootPath ) ; throw error ; } await this . openProject ( createTimeScaffoldType , openInNewWindow , OpenScenario . createNewProject ) ; } async openProject ( scaffoldType , openInNewWindow , openScenario ) { await this . loadWorkspaceConfigFilePath ( scaffoldType ) ; if ( ! ( await FileUtility . fileExists ( scaffoldType , this . workspaceConfigFilePath ) ) ) { throw new FileNotFoundError ( "open project" , `workspace configuration file ${ this . workspaceConfigFilePath }` , "Please initialize the project first.`" ) ; } if ( ! openInNewWindow ) { try { const telemetryWorker = TelemetryWorker . getInstance ( this . extensionContext ) ; const eventNames = openScenario === OpenScenario . createNewProject ? EventNames . createNewProjectEvent : EventNames . configProjectEnvironmentEvent ; telemetryWorker . sendEvent ( eventNames , this . telemetryContext ) ; } catch { } } vscode . commands . executeCommand ( IoTCubeCommands . OpenLocally , this . workspaceConfigFilePath , openInNewWindow ) ; } param boardId param deviceRootPath param scaffoldType param templateFilesInfo private initDeviceComponents ( boardId , deviceRootPath , templateFilesInfo ? ) { if ( ! deviceRootPath ) { throw new ArgumentEmptyOrNullError ( "initialize device" , `device root path: ${ deviceRootPath }` , "Please initialize the project first." ) ; } let device ; if ( boardId === az3166DeviceModule . AZ3166Device . boardId ) { device = new az3166DeviceModule . AZ3166Device ( this . extensionContext , this . channel , this . telemetryContext , deviceRootPath , templateFilesInfo ) ; } else if ( boardId === esp32DeviceModule . Esp32Device . boardId ) { device = new esp32DeviceModule . Esp32Device ( this . extensionContext , this . channel , this . telemetryContext , deviceRootPath , templateFilesInfo ) ; } else { throw new TypeNotSupportedError ( "board type" , boardId ) ; } if ( device ) { this . componentList . push ( device ) ; } } param scaffoldType private async initAzureComponentsWithoutConfig ( scaffoldType ) { const iotHub = new ioTHubModule . IoTHub ( this . projectRootPath , this . channel ) ; await iotHub . updateConfigSettings ( scaffoldType ) ; this . componentList . push ( iotHub ) ; const iotHubDevice = new ioTHubDeviceModule . IoTHubDevice ( this . projectRootPath , this . channel , [ { component : iotHub , type : azureComponentConfigModule . DependencyType . Input } ] ) ; await iotHubDevice . updateConfigSettings ( scaffoldType ) ; this . componentList . push ( iotHubDevice ) ; const functionPath = ConfigHandler . get < string > ( ConfigKey . functionPath ) ; if ( functionPath ) { const functionLocation = path . join ( this . projectRootPath , functionPath ) ; const functionApp = new azureFunctionsModule . AzureFunctions ( this . projectRootPath , functionLocation , functionPath , this . channel , null , [ { component : iotHub , type : azureComponentConfigModule . DependencyType . Input } ] ) ; await functionApp . updateConfigSettings ( scaffoldType ) ; this . componentList . push ( functionApp ) ; } } param scaffoldType param componentConfigs private async loadAzureComponentsByConfig ( componentConfigs ) { for ( const componentConfig of componentConfigs ) { switch ( componentConfig . type ) { case ComponentType . IoTHub : { const iotHub = new ioTHubModule . IoTHub ( this . projectRootPath , this . channel ) ; await iotHub . load ( ) ; this . componentList . push ( iotHub ) ; const iothubDevice = new ioTHubDeviceModule . IoTHubDevice ( this . projectRootPath , this . channel , [ { component : iotHub , type : azureComponentConfigModule . DependencyType . Input } ] ) ; await iothubDevice . load ( ) ; this . componentList . push ( iothubDevice ) ; break ; } case ComponentType . IoTHubDevice : { break ; } case ComponentType . AzureFunctions : { const functionPath = ConfigHandler . get < string > ( ConfigKey . functionPath ) ; if ( ! functionPath ) { throw new WorkspaceConfigNotFoundError ( ConfigKey . functionPath ) ; } const functionLocation = path . join ( this . projectRootPath , functionPath ) ; if ( functionLocation ) { const functionApp = new azureFunctionsModule . AzureFunctions ( this . projectRootPath , functionLocation , functionPath , this . channel ) ; await functionApp . load ( ) ; this . componentList . push ( functionApp ) ; } break ; } default : { throw new TypeNotSupportedError ( "component type" , `${ componentConfig . type }` ) ; } } } } param scaffoldType private async loadAzureConfigAndComponents ( scaffoldType ) { const azureConfigFileHandler = new azureComponentConfigModule . AzureConfigFileHandler ( this . projectRootPath ) ; await azureConfigFileHandler . createIfNotExists ( scaffoldType ) ; const componentConfigs = await azureConfigFileHandler . getSortedComponents ( scaffoldType ) ; if ( componentConfigs . length === 0 ) { await this . initAzureComponentsWithoutConfig ( scaffoldType ) ; } else { await this . loadAzureComponentsByConfig ( componentConfigs ) ; } } param projectType param workspaceConfig private initAzureComponents ( projectType , workspaceConfig ) { if ( ! this . projectRootPath ) { throw new ArgumentEmptyOrNullError ( "init azure components" , `project root path: ${ this . projectRootPath }` , "Please initialize the project first." ) ; } switch ( projectType ) { case ProjectTemplateType . Basic : break ; case ProjectTemplateType . IotHub : { const iothub = new ioTHubModule . IoTHub ( this . projectRootPath , this . channel ) ; this . componentList . push ( iothub ) ; break ; } case ProjectTemplateType . AzureFunctions : { const iothub = new ioTHubModule . IoTHub ( this . projectRootPath , this . channel ) ; const functionDir = path . join ( this . projectRootPath , IoTWorkspaceProject . folderName . functionDefaultFolderName ) ; const azureFunctions = new azureFunctionsModule . AzureFunctions ( this . projectRootPath , functionDir , IoTWorkspaceProject . folderName . functionDefaultFolderName , this . channel , null , [ { component : iothub , type : azureComponentConfigModule . DependencyType . Input } ] ) ; workspaceConfig . folders . push ( { path : IoTWorkspaceProject . folderName . functionDefaultFolderName } ) ; workspaceConfig . settings [ `IoTWorkbench.${ ConfigKey . functionPath }` ] = IoTWorkspaceProject . folderName . functionDefaultFolderName ; this . componentList . push ( iothub ) ; this . componentList . push ( azureFunctions ) ; break ; } default : break ; } } param scaffoldType private async loadWorkspaceConfigFilePath ( scaffoldType ) { await this . validateProjectRootPathExists ( "init workspace config file path" , scaffoldType ) ; const workspaceFile = getWorkspaceFile ( this . projectRootPath ) ; if ( ! workspaceFile ) { throw new FileNotFoundError ( "init iot project workspace file path" , `workspace file under project root path: ${ this . projectRootPath }.` , "" ) ; } this . workspaceConfigFilePath = path . join ( this . projectRootPath , workspaceFile ) ; } }