'use strict' ; import * as vscode from 'vscode' ; import * as fs from 'fs-extra' ; import * as path from 'path' ; import { UserInputUtil } from './UserInputUtil' ; import { Reporter } from '../util/Reporter' ; import { PackageRegistryEntry } from '../registries/PackageRegistryEntry' ; import { VSCodeBlockchainOutputAdapter } from '../logging/VSCodeBlockchainOutputAdapter' ; import { LogType , FileSystemUtil } from 'ibm-blockchain-platform-common' ; import { ExtensionCommands } from '../../ExtensionCommands' ; import { SettingConfigurations } from '../configurations' ; import { DeployView } from '../webview/DeployView' ; import { CommandUtil } from '../util/CommandUtil' ; interface IProperties { workspacePackageName : string ; workspacePackageVersion : string ; } /**
 * Main function which calls the methods and refreshes the blockchain explorer box each time that it runs successfully.
 * This will be used in other files to call the command to package a smart contract project.
 */ export async function packageSmartContract ( workspace ? , overrideName ? , overrideVersion ? , overrideFabricVersion ? ) { const outputAdapter = VSCodeBlockchainOutputAdapter . instance ( ) ; outputAdapter . log ( LogType . INFO , undefined , 'packageSmartContract' ) ; let resolvedPkgDir ; let properties ; let language ; let packageError ; let fabricVersion = overrideFabricVersion ; try { const extDir = SettingConfigurations . getExtensionDir ( ) ; const pkgDir = path . join ( extDir , 'packages' ) ; resolvedPkgDir = FileSystemUtil . getDirPath ( pkgDir ) ; await fs . ensureDir ( resolvedPkgDir ) ; if ( ! workspace || ! workspace . uri ) { workspace = await UserInputUtil . chooseWorkspace ( true ) ; if ( ! workspace ) { return ; } } await buildWorkspace ( workspace ) ; checkForProjectErrors ( workspace ) ; const fabricVersions = [ { version : 2 , label : UserInputUtil . SELECT_PACKAGING_FABRIC_VERSION_2 } , { version : 1 , label : UserInputUtil . SELECT_PACKAGING_FABRIC_VERSION_1 } , ] ; if ( ! overrideFabricVersion || fabricVersions . findIndex ( ( { version } ) => version === overrideFabricVersion ) === - 1 ) { const fabricVersionString = await UserInputUtil . showQuickPick ( 'Select the packaging output format' , fabricVersions . map ( ( { label } ) => label ) ) as string ; if ( ! fabricVersionString ) { return ; } const selectedOption = fabricVersions . find ( ( { label } ) => label === fabricVersionString ) ; fabricVersion = selectedOption . version ; } language = await UserInputUtil . getLanguage ( workspace ) ; if ( language === 'golang' ) { properties = await golangPackageAndVersion ( overrideName , overrideVersion ) ; packageError = 'Go package name' ; } else if ( language === 'java' ) { properties = await javaPackageAndVersion ( overrideName , overrideVersion ) ; packageError = 'Java package name' ; } else { properties = await packageJsonNameAndVersion ( workspace , overrideName , overrideVersion ) ; packageError = 'package.json name' ; } if ( ! properties ) { return ; } const regex = /^[a-zA-Z0-9-_]+$/ ; const replaceRegex = /@.*?\// ; properties . workspacePackageName = properties . workspacePackageName . replace ( replaceRegex , '' ) ; const validPackageName = regex . test ( properties . workspacePackageName ) ; if ( ! validPackageName ) { outputAdapter . log ( LogType . ERROR , `Invalid ${ packageError }. Name can only include alphanumeric, "_" and "-" characters.` ) ; return ; } } catch ( err ) { outputAdapter . log ( LogType . ERROR , err . message , err . toString ( ) ) ; return ; } return vscode . window . withProgress ( { location : vscode . ProgressLocation . Notification , title : 'IBM Blockchain Platform Extension' , cancellable : false } , async ( progress ) => { progress . report ( { message : `Packaging Smart Contract` } ) ; let originalGOPATH = '' ; try { const pkgFileExtension = ( fabricVersion === 1 ) ? 'cds' : 'tar.gz' ; const pkgFile = path . join ( resolvedPkgDir , `${ properties . workspacePackageName }@${ properties . workspacePackageVersion }.${ pkgFileExtension }` ) ; let pkgFileExists = await fs . pathExists ( pkgFile ) ; if ( ! pkgFileExists && fabricVersion === 2 ) { const altPkgFile = path . join ( resolvedPkgDir , `${ properties . workspacePackageName }@${ properties . workspacePackageVersion }.tgz` ) ; pkgFileExists = await fs . pathExists ( altPkgFile ) ; } if ( pkgFileExists ) { if ( language === 'golang' ) { throw new Error ( 'Package with name and version already exists. Please input a different name or version for your Go project.' ) ; } else if ( language === 'java' ) { throw new Error ( 'Package with name and version already exists. Please input a different name or version for your Java project.' ) ; } else { throw new Error ( 'Package with name and version already exists. Please change the name and/or the version of the project in your package.json file.' ) ; } } let contractPath = workspace . uri . fsPath ; const contractPathToVendor = contractPath ; if ( language === 'golang' ) { const isModule = await fs . pathExists ( path . join ( contractPath , 'go.mod' ) ) ; if ( fabricVersion === 1 || ! isModule ) { if ( ! process . env . GOPATH ) { const indexSrc = contractPath . indexOf ( path . sep + 'src' + path . sep ) ; if ( indexSrc === - 1 ) { throw new Error ( 'The environment variable GOPATH has not been set, and the extension was not able to automatically detect the correct value. You cannot package this Go smart contract without setting the environment variable GOPATH.' ) ; } else { const srcPath = contractPath . substring ( 0 , indexSrc + 4 ) ; contractPath = path . relative ( srcPath , contractPath ) ; process . env . GOPATH = path . join ( srcPath , '..' ) ; } } else { const indexSrc = contractPath . indexOf ( path . sep + 'src' + path . sep ) ; let pathsMatch = false ; if ( indexSrc !== - 1 ) { const srcPath = contractPath . substring ( 0 , indexSrc + 4 ) ; const goPaths = process . env . GOPATH . split ( path . delimiter ) ; if ( goPaths . length > 1 ) { originalGOPATH = process . env . GOPATH ; } goPaths . forEach ( ( value ) => { if ( value . charAt ( value . length - 1 ) === path . sep ) { value = value . substr ( 0 , value . length - 1 ) ; } if ( value === srcPath . substr ( 0 , srcPath . length - 4 ) ) { process . env . GOPATH = value ; pathsMatch = true ; } } ) ; contractPath = path . relative ( srcPath , contractPath ) ; } if ( ! pathsMatch || ! contractPath || contractPath . startsWith ( '..' ) || path . isAbsolute ( contractPath ) ) { throw new Error ( 'The Go smart contract is not a subdirectory of the path specified by the environment variable GOPATH. Please correct the environment variable GOPATH.' ) ; } } } if ( isModule ) { await CommandUtil . sendCommandWithOutput ( 'go' , [ 'mod' , 'vendor' ] , contractPathToVendor ) ; } } let metadataPath = path . join ( workspace . uri . fsPath , 'META-INF' ) ; const metadataPathExists = await fs . pathExists ( metadataPath ) ; if ( ! metadataPathExists ) { metadataPath = null ; } const { PackageSmartContract } = await import ( 'ibm-blockchain-platform-environment-v1' ) ; const fileNames = await PackageSmartContract . packageContract ( fabricVersion , properties . workspacePackageName , properties . workspacePackageVersion , contractPath , pkgFile , language , metadataPath ) ; Reporter . instance ( ) . sendTelemetryEvent ( 'packageCommand' ) ; await vscode . commands . executeCommand ( ExtensionCommands . REFRESH_PACKAGES ) ; outputAdapter . log ( LogType . SUCCESS , `Smart Contract packaged: ${ pkgFile }` ) ; outputAdapter . log ( LogType . INFO , undefined , `${ fileNames . length } file(s) packaged:` ) ; for ( const file of fileNames ) { outputAdapter . log ( LogType . INFO , undefined , `- ${ file }` ) ; } const packageEntry = new PackageRegistryEntry ( ) ; packageEntry . name = properties . workspacePackageName ; packageEntry . version = properties . workspacePackageVersion ; packageEntry . path = pkgFile ; const stat = await fs . lstat ( pkgFile ) ; const sizeKB = Math . round ( stat . size / 1000 ) ; packageEntry . sizeKB = sizeKB ; const panel = DeployView . panel ; if ( panel ) { await DeployView . updatePackages ( ) ; } if ( originalGOPATH ) { process . env . GOPATH = originalGOPATH ; } return packageEntry ; } catch ( err ) { outputAdapter . log ( LogType . ERROR , err . message , err . toString ( ) ) ; if ( originalGOPATH ) { process . env . GOPATH = originalGOPATH ; } return ; } } ) ; } param workspaceDir { String } returns { string async function packageJsonNameAndVersion ( workspaceDir , overrideName ? , overrideVersion ? ) { const workspacePackage = path . join ( workspaceDir . uri . fsPath , '/package.json' ) ; const workspacePackageContents = await fs . readFile ( workspacePackage ) ; const workspacePackageObj = JSON . parse ( workspacePackageContents . toString ( 'utf8' ) ) ; let workspacePackageName = workspacePackageObj . name ; let workspacePackageVersion = workspacePackageObj . version ; if ( overrideName ) { workspacePackageName = overrideName ; } if ( overrideVersion ) { workspacePackageVersion = overrideVersion ; } if ( ! workspacePackageName || ! workspacePackageVersion ) { const message = 'Please enter a package name and/or package version into your package.json' ; throw new Error ( message ) ; } return { workspacePackageName , workspacePackageVersion } ; } param workspaceDir { String } returns { string async function javaPackageAndVersion ( overrideName ? , overrideVersion ? ) { let workspacePackageName = overrideName ; if ( ! workspacePackageName ) { workspacePackageName = await UserInputUtil . showInputBox ( 'Enter a name for your Java package' ) ; if ( ! workspacePackageName ) { return ; } } let workspacePackageVersion = overrideVersion ; if ( ! workspacePackageVersion ) { workspacePackageVersion = await UserInputUtil . showInputBox ( 'Enter a version for your Java package' ) ; if ( ! workspacePackageVersion ) { return ; } } return { workspacePackageName , workspacePackageVersion } ; } param workspaceDir { String } returns { string async function golangPackageAndVersion ( overrideName ? , overrideVersion ? ) { let workspacePackageName = overrideName ; if ( ! workspacePackageName ) { workspacePackageName = await UserInputUtil . showInputBox ( 'Enter a name for your Go package' ) ; if ( ! workspacePackageName ) { return ; } } let workspacePackageVersion = overrideVersion ; if ( ! workspacePackageVersion ) { workspacePackageVersion = await UserInputUtil . showInputBox ( 'Enter a version for your Go package' ) ; if ( ! workspacePackageVersion ) { return ; } } return { workspacePackageName , workspacePackageVersion } ; } function checkForProjectErrors ( workspaceDir ) { const collections = vscode . languages . getDiagnostics ( ) ; for ( const collection of collections ) { for ( const thing of collection ) { if ( thing instanceof vscode . Uri ) { const uri = thing ; const relativePath = path . relative ( workspaceDir . uri . fsPath , uri . fsPath ) ; if ( ! relativePath || relativePath . startsWith ( '..' ) || path . isAbsolute ( relativePath ) ) { break ; } } else { const diagnostics = thing ; for ( const diagnostic of diagnostics ) { if ( diagnostic . severity === 0 ) { throw new Error ( 'Smart contract project has errors please fix them before packaging' ) ; } } } } } } async function buildWorkspace ( workspaceDir ) { const tasks = await vscode . tasks . fetchTasks ( ) ; const buildTasks = tasks . filter ( ( task ) => { if ( ! task . scope || task . scope === vscode . TaskScope . Global || task . scope === vscode . TaskScope . Workspace ) { return false ; } else if ( task . scope . uri . fsPath !== workspaceDir . uri . fsPath ) { return false ; } else if ( task . group !== vscode . TaskGroup . Build ) { return false ; } else if ( task . isBackground ) { return false ; } else if ( task . name . match ( /watch/i ) ) { return false ; } else { return true ; } } ) ; if ( buildTasks . length > 0 ) { const buildTask = buildTasks [ 0 ] ; await vscode . tasks . executeTask ( buildTask ) ; await new Promise ( ( resolve ) : any => { const buildTaskListener = vscode . tasks . onDidEndTask ( ( _e ) => { buildTaskListener . dispose ( ) ; resolve ( ) ; } ) ; } ) ; } }