2017-04-18 18:18:22 +03:00
/ *
* Copyright ( c ) Microsoft Corporation . All rights reserved .
* Licensed under the MIT License . See License . txt in the project root for
* license information .
* /
2016-02-28 06:30:26 +03:00
2017-03-18 21:43:33 +03:00
const gulp = require ( 'gulp' ) ;
const args = require ( 'yargs' ) . argv ;
const colors = require ( 'colors' ) ;
const fs = require ( 'fs' ) ;
const util = require ( 'util' ) ;
const path = require ( 'path' ) ;
2017-03-25 22:15:52 +03:00
const glob = require ( 'glob' ) ;
2017-03-18 21:43:33 +03:00
const execSync = require ( 'child_process' ) . execSync ;
2016-02-28 06:30:26 +03:00
2017-08-26 04:53:43 +03:00
var mappings = require ( './codegen_mappings.json' ) ;
2016-02-28 06:30:26 +03:00
2017-08-26 04:53:43 +03:00
const defaultAutoRestVersion = '1.2.2' ;
2016-03-01 10:11:50 +03:00
var usingAutoRestVersion ;
2017-08-26 04:53:43 +03:00
const specRepoDir = args [ 'spec-repo-dir' ] ;
const specRoot = args [ 'spec-root' ] || "https://raw.githubusercontent.com/Azure/azure-rest-api-specs/current/specification" ;
2017-03-18 21:43:33 +03:00
const project = args [ 'project' ] ;
2016-03-01 05:32:11 +03:00
var language = 'Azure.NodeJS' ;
2016-03-01 10:11:50 +03:00
var modeler = 'Swagger' ;
2017-09-29 21:41:15 +03:00
const regexForExcludedServices = /\/(intune|documentdbManagement|insightsManagement|insights|search)\//i ;
2016-03-01 05:32:11 +03:00
2017-03-18 21:43:33 +03:00
function getAutorestVersion ( version ) {
if ( ! version ) version = 'latest' ;
let getVersion , execHelp ;
let result = true ;
2016-03-01 10:11:50 +03:00
try {
2017-09-26 01:43:58 +03:00
let getVersionCmd = ` autorest ` ;
2017-03-18 21:43:33 +03:00
let execHelpCmd = ` autorest --help ` ;
console . log ( getVersionCmd ) ;
getVersion = execSync ( getVersionCmd , { encoding : 'utf8' } ) ;
//console.debug(getVersion);
console . log ( execHelpCmd ) ;
execHelp = execSync ( execHelpCmd , { encoding : 'utf8' } ) ;
//console.debug(execHelp);
2016-03-01 10:11:50 +03:00
} catch ( err ) {
2017-03-18 21:43:33 +03:00
result = false ;
console . log ( ` An error occurred while getting the " ${ version } " of autorest and executing "autorest --help": \n ${ util . inspect ( err , { depth : null } )}. ` ) ;
2016-03-01 10:11:50 +03:00
}
2017-03-18 21:43:33 +03:00
return result ;
2016-03-01 10:11:50 +03:00
}
2017-03-25 22:59:14 +03:00
function deleteFolderRecursive ( path ) {
if ( fs . existsSync ( path ) ) {
fs . readdirSync ( path ) . forEach ( function ( file , index ) {
var curPath = path + "/" + file ;
if ( fs . lstatSync ( curPath ) . isDirectory ( ) ) { // recurse
deleteFolderRecursive ( curPath ) ;
} else { // delete file
fs . unlinkSync ( curPath ) ;
}
2016-03-01 10:11:50 +03:00
} ) ;
2017-03-25 22:59:14 +03:00
fs . rmdirSync ( path ) ;
2016-03-01 10:11:50 +03:00
}
2017-03-25 22:59:14 +03:00
} ;
2017-03-25 22:15:52 +03:00
function clearProjectBeforeGenerating ( projectDir ) {
let modelsDir = ` ${ projectDir } /models ` ;
let operationsDir = ` ${ projectDir } /operations ` ;
2017-03-26 05:50:52 +03:00
let clientTypedefFile = path . basename ( glob . sync ( ` ${ projectDir } /*.d.ts ` ) [ 0 ] || '' ) ;
2017-03-25 22:15:52 +03:00
let clientJSFile = ` ${ clientTypedefFile . split ( '.' ) [ 0 ] } .js ` ;
let directoriesToBeDeleted = [ modelsDir , operationsDir ] ;
let filesToBeDeleted = [ clientTypedefFile , clientJSFile ] ;
directoriesToBeDeleted . forEach ( ( dir ) => {
if ( fs . existsSync ( dir ) ) {
2017-03-25 22:59:14 +03:00
deleteFolderRecursive ( dir ) ;
2017-03-25 22:15:52 +03:00
}
} ) ;
filesToBeDeleted . forEach ( ( file ) => {
if ( fs . existsSync ( file ) ) {
fs . unlinkSync ( file ) ;
}
} ) ;
return ;
2016-03-01 10:11:50 +03:00
}
2017-08-26 04:53:43 +03:00
function generateProject ( projectObj , specRoot , autoRestVersion ) {
let specPath = specRoot + '/' + projectObj . source ;
let isInputJson = projectObj . source . endsWith ( "json" ) ;
2017-03-18 21:43:33 +03:00
let result ;
2017-08-26 04:53:43 +03:00
const azureTemplate = 'Azure.NodeJs' ;
language = azureTemplate ;
2016-03-01 05:32:11 +03:00
//servicefabric wants to generate using generic NodeJS.
2017-08-26 04:53:43 +03:00
if ( projectObj . language && projectObj . language . match ( /^NodeJS$/ig ) !== null ) {
language = projectObj . language ;
2016-03-01 10:11:50 +03:00
}
2017-08-26 04:53:43 +03:00
let packageName = projectObj . packageName ;
2017-09-28 02:32:53 +03:00
console . log ( ` \n >>>>>>>>>>>>>>>>>>>Start: " ${ packageName } " >>>>>>>>>>>>>>>>>>>>>>>>> ` ) ;
let outputDir = ` lib/services/ ${ projectObj . dir } ` ;
let cmd = 'autorest ' ;
if ( projectObj . batchGeneration ) {
cmd += ` --nodejs-sdks-folder= ${ _ _dirname } / ${ outputDir } --package-name= ${ packageName } --nodejs --license-header=MICROSOFT_MIT_NO_VERSION ` ;
} else {
cmd += ` --output-folder= ${ _ _dirname } / ${ outputDir } --package-name= ${ packageName } --nodejs --license-header=MICROSOFT_MIT_NO_VERSION ` ;
}
2017-08-26 04:53:43 +03:00
// if using azure template, pass in azure-arm argument. otherwise, get the generic template by not passing in anything.
if ( language === azureTemplate ) cmd += ' --azure-arm ' ;
if ( isInputJson ) {
cmd += ` --input-file= ${ specPath } ` ;
}
else {
cmd += ` ${ specPath } ` ;
}
if ( projectObj . ft !== null && projectObj . ft !== undefined ) cmd += ' --payload-flattening-threshold=' + projectObj . ft ;
if ( projectObj . clientName !== null && projectObj . clientName !== undefined ) cmd += ' --override-client-name=' + projectObj . clientName ;
2017-09-28 02:32:53 +03:00
if ( projectObj . tag !== null && projectObj . tag !== undefined ) cmd += ` --tag= ${ projectObj . tag } ` ;
2017-08-26 04:53:43 +03:00
if ( projectObj . args !== undefined ) {
2016-02-28 06:30:26 +03:00
cmd = cmd + ' ' + args ;
}
2017-03-18 21:43:33 +03:00
try {
2017-09-28 02:32:53 +03:00
//console.log(`Cleaning the output directory: "${outputDir}".`);
//clearProjectBeforeGenerating(outputDir);
2017-03-18 21:43:33 +03:00
console . log ( 'Executing command:' ) ;
console . log ( '------------------------------------------------------------' ) ;
console . log ( cmd ) ;
console . log ( '------------------------------------------------------------' ) ;
result = execSync ( cmd , { encoding : 'utf8' } ) ;
console . log ( 'Output:' ) ;
console . log ( result ) ;
} catch ( err ) {
console . log ( 'Error:' ) ;
2017-09-28 02:32:53 +03:00
console . log ( ` An error occurred while generating client for package: " ${ packageName } ": \n ${ err . stderr } ` ) ;
2017-03-18 21:43:33 +03:00
}
2017-09-28 02:32:53 +03:00
console . log ( ` >>>>>>>>>>>>>>>>>>>>>End: " ${ packageName } " >>>>>>>>>>>>>>>>>>>>>>>>> \n ` ) ;
2017-03-18 21:43:33 +03:00
return ;
}
function installAutorest ( ) {
let installation ;
let isSuccessful = true ;
let autorestAlreadyInstalled = true ;
try {
execSync ( ` autorest --help ` ) ;
} catch ( error ) {
autorestAlreadyInstalled = false ;
}
try {
if ( ! autorestAlreadyInstalled ) {
console . log ( 'Looks like autorest is not installed on your machine. Installing autorest . . .' ) ;
let installCmd = 'npm install -g autorest' ;
console . log ( installCmd ) ;
installation = execSync ( installCmd , { encoding : 'utf8' } ) ;
//console.debug('installation');
}
isSuccessful = getAutorestVersion ( ) ;
} catch ( err ) {
isSuccessful = false ;
console . log ( ` An error occurred while installing autorest via npm: \n ${ util . inspect ( err , { depth : null } )}. ` ) ;
}
return isSuccessful ;
}
2017-08-26 04:53:43 +03:00
function codegen ( projectObj , index ) {
2017-09-28 02:32:53 +03:00
let versionSuccessfullyFound = true ;
2017-08-26 04:53:43 +03:00
let usingAutoRestVersion = defaultAutoRestVersion ;
function checkAutorestVersion ( actualProj ) {
if ( actualProj . autoRestVersion ) {
usingAutoRestVersion = actualProj . autoRestVersion ;
}
if ( index === 0 ) {
versionSuccessfullyFound = getAutorestVersion ( usingAutoRestVersion ) ;
if ( ! versionSuccessfullyFound ) {
process . exit ( 1 ) ;
}
}
2017-03-18 21:43:33 +03:00
}
2017-08-26 04:53:43 +03:00
function iterateProject ( proj , specRoot , usingAutoRestVersion ) {
for ( key in proj ) {
if ( proj [ key ] [ 'packageName' ] ) {
if ( ! versionSuccessfullyFound ) {
checkAutorestVersion ( proj [ key ] , index ) ;
}
generateProject ( proj [ key ] , specRoot , usingAutoRestVersion ) ;
} else {
iterateProject ( proj [ key ] , specRoot , usingAutoRestVersion ) ;
}
2017-03-18 21:43:33 +03:00
}
}
2017-08-26 04:53:43 +03:00
return iterateProject ( projectObj , specRoot , usingAutoRestVersion ) ;
2016-03-01 05:32:11 +03:00
}
2016-02-28 06:30:26 +03:00
2017-03-18 21:43:33 +03:00
gulp . task ( 'default' , function ( ) {
2016-02-28 06:30:26 +03:00
console . log ( "Usage: gulp codegen [--spec-root <swagger specs root>] [--project <project name>]\n" ) ;
console . log ( "--spec-root" ) ;
console . log ( "\tRoot location of Swagger API specs, default value is \"https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master\"" ) ;
console . log ( "--project\n\tProject to regenerate, default is all. List of available project names:" ) ;
2017-03-18 21:43:33 +03:00
Object . keys ( mappings ) . forEach ( function ( i ) {
console . log ( '\t' + i . magenta ) ;
2016-02-28 06:30:26 +03:00
} ) ;
} ) ;
2017-04-03 21:00:32 +03:00
//This task is used to generate libraries based on the mappings specified above.
2017-03-18 21:43:33 +03:00
gulp . task ( 'codegen' , function ( cb ) {
2017-06-30 21:17:03 +03:00
if ( project === undefined ) {
let arr = Object . keys ( mappings ) ;
for ( let i = 0 ; i < arr . length ; i ++ ) {
2017-09-28 02:32:53 +03:00
codegen ( mappings [ arr [ i ] ] , i ) ;
2016-02-28 06:30:26 +03:00
}
2017-03-18 21:43:33 +03:00
} else {
2017-06-30 21:17:03 +03:00
if ( mappings [ project ] === undefined ) {
console . error ( 'Invalid project name "' + project + '"!' ) ;
process . exit ( 1 ) ;
}
2017-08-26 04:53:43 +03:00
codegen ( mappings [ project ] , null ) ;
2017-03-18 21:43:33 +03:00
}
2017-04-02 00:47:59 +03:00
} ) ;
2017-04-03 21:00:32 +03:00
//This task validates that the entry in "main" and "types" in package.json points to a file that exists on the disk.
// for best results run on mac or linux. Windows is case insenstive for file paths. Hence it will not catch those issues.
//If not tested this will cause "module not found" errors for customers when they try to use the package.
2017-04-02 00:47:59 +03:00
gulp . task ( 'validate-each-packagejson' , ( cb ) => {
let packagePaths = glob . sync ( path . join ( _ _dirname , '/lib/services' , '/**/package.json' ) ) ;
packagePaths . forEach ( ( packagePath ) => {
const package = require ( packagePath ) ;
//console.log(package);
if ( package . name . startsWith ( 'azure-arm-' ) ) {
console . log ( ` Validating package: ${ package . name } ` ) ;
if ( package . main ) {
let mainPath = path . resolve ( path . dirname ( packagePath ) , package . main ) ;
if ( ! fs . existsSync ( mainPath ) ) console . log ( ` \t > ${ mainPath } does not exist. ` ) ;
} else {
console . log ( ` \t >Could not find "main" entry in package.json for ${ packagePath } . ` ) ;
}
if ( package . types ) {
let typesPath = path . resolve ( path . dirname ( packagePath ) , package . types ) ;
if ( ! fs . existsSync ( typesPath ) ) console . log ( ` \t > ${ typesPath } does not exist. ` ) ;
} else {
console . log ( ` \t >Could not find "types" entry in package.json for ${ packagePath } . ` ) ;
}
}
} ) ;
} ) ;
2017-04-03 21:00:32 +03:00
//This task updates the dependencies in package.json to the relative service libraries inside lib/services directory.
2017-04-02 00:47:59 +03:00
gulp . task ( 'update-deps-rollup' , ( cb ) => {
2017-09-29 21:41:15 +03:00
2017-09-29 01:48:38 +03:00
let packagePaths = glob . sync ( path . join ( _ _dirname , './lib/services' , '/**/package.json' ) ) . filter ( ( packagePath ) => {
2017-09-29 21:41:15 +03:00
return packagePath . match ( regexForExcludedServices ) === null ;
2017-09-29 01:48:38 +03:00
} ) ;
2017-04-02 00:47:59 +03:00
let rollupPackage = require ( './package.json' ) ;
let rollupDependencies = rollupPackage . dependencies ;
rollupDependencies [ 'ms-rest' ] = './runtime/ms-rest' ;
rollupDependencies [ 'ms-rest-azure' ] = './runtime/ms-rest-azure' ;
packagePaths . forEach ( ( packagePath ) => {
const package = require ( packagePath ) ;
//console.log(package);
let packageName = package . name
packageDir = path . dirname ( packagePath ) ;
if ( rollupDependencies [ packageName ] ) {
rollupDependencies [ packageName ] = packageDir ;
} else {
console . log ( ` Could not find ${ packageName } as a dependecy in rollup package.json file.. ` ) ;
2016-02-28 06:30:26 +03:00
}
} ) ;
2017-04-03 21:00:32 +03:00
fs . writeFileSync ( './package.json' , JSON . stringify ( rollupPackage , null , 2 ) , { 'encoding' : 'utf8' } ) ;
} ) ;
//This task ensures that all the exposed createSomeClient() methods, can correctly instantiate clients. By doing this we test,
//that the "main" entry in package.json points to a file at the correct location. We test the signature of the client constructor
//is as expected. As of now HD Isnight is expected to fail as it is still using the Hyak generator. Once it moves to Autorest, it should
2017-09-28 06:01:40 +03:00
//not fail. Before executing this task, execute `gulp update-deps-rollup`, `rm -rf node_modules` and `npm install` so that the changes inside the sdks in lib/services
//are installed inside the node_modules folder.
2017-04-03 21:00:32 +03:00
gulp . task ( 'test-create-rollup' , ( cb ) => {
const azure = require ( './lib/azure' ) ;
2017-04-03 22:22:24 +03:00
const keys = Object . keys ( azure ) . filter ( ( key ) => { return key . startsWith ( 'create' ) && ! key . startsWith ( 'createASM' ) && key . endsWith ( 'Client' ) && key !== 'createSchedulerClient' } ) ;
2017-04-03 21:00:32 +03:00
//console.dir(keys);
//console.log(keys.length);
const creds = { signRequest : { } } ;
const subId = '1234556' ;
keys . forEach ( ( key ) => {
console . log ( key ) ;
const Client = azure [ key ] ;
var c ;
try {
if ( key === 'createKeyVaultClient' || key === 'createSubscriptionManagementClient' ||
key === 'createDataLakeAnalyticsJobManagementClient' || key === 'createDataLakeStoreFileSystemManagementClient' ||
key === 'createDataLakeAnalyticsCatalogManagementClient' ) {
c = new Client ( creds ) ;
2017-09-28 06:01:40 +03:00
} else if ( key === 'createServiceFabricClient' ) {
c = new Client ( ) ;
2017-04-03 21:00:32 +03:00
} else {
c = new Client ( creds , subId ) ;
}
//console.dir(Object.keys(c));
} catch ( err ) {
console . dir ( err ) ;
}
} ) ;
2017-04-18 18:18:22 +03:00
} ) ;
2017-08-26 04:53:43 +03:00
// This task updates the codegen_mappings.json file in sync with the azure-rest-api-specs public repo.
gulp . task ( 'sync-mappings-with-repo' , ( cb ) => {
if ( ! specRepoDir ) {
return cb ( new Error ( 'Please provide --spec-repo-dir <Absolute path to the directory where the azure-rest-api-specs is cloned.>' ) ) ;
}
let specDir = ` ${ specRepoDir } /specification ` ;
const dirs = fs . readdirSync ( specDir ) . filter ( f => fs . statSync ( ` ${ specDir } / ${ f } ` ) . isDirectory ( ) ) ;
let newlyAdded = [ ] ;
2017-09-26 01:43:58 +03:00
let originalProjectCount = Object . keys ( mappings ) . length ;
2017-08-26 04:53:43 +03:00
for ( let rp of dirs ) {
2017-09-28 02:32:53 +03:00
if ( rp . toLowerCase ( ) === 'intune' || rp . toLowerCase ( ) === 'azsadmin' || rp . toLowerCase ( ) === 'timeseriesinsights' ) continue ;
2017-08-26 04:53:43 +03:00
let rm = ` ${ specRepoDir } /specification/ ${ rp } /resource-manager ` ;
let dp = ` ${ specRepoDir } /specification/ ${ rp } /data-plane ` ;
if ( ! mappings [ rp ] ) {
mappings [ rp ] = { } ;
if ( fs . existsSync ( rm ) ) {
mappings [ rp ] [ 'resource-manager' ] = {
"packageName" : ` azure-arm- ${ rp . toLowerCase ( ) } ` ,
"dir" : ` ${ rp } Management/lib ` ,
"source" : ` ${ rp } /resource-manager/readme.md `
}
newlyAdded . push ( ` ${ rp } ['resource-manager'] ` ) ;
console . log ( ` Updating RP: ${ rp } , "resource-manager". ` ) ;
console . dir ( mappings [ rp ] [ 'resource-manager' ] , { depth : null , colors : true } ) ;
}
if ( fs . existsSync ( dp ) ) {
mappings [ rp ] [ 'data-plane' ] = {
"packageName" : ` azure- ${ rp . toLowerCase ( ) } ` ,
"dir" : ` ${ rp } /lib ` ,
"source" : ` ${ rp } /data-plane/readme.md `
}
newlyAdded . push ( ` ${ rp } ['data-plane'] ` ) ;
console . log ( ` Updating RP: ${ rp } , "data-plane". ` ) ;
console . dir ( mappings [ rp ] [ 'data-plane' ] , { depth : null , colors : true } ) ;
}
} else {
if ( fs . existsSync ( rm ) && ! mappings [ rp ] [ 'resource-manager' ] ) {
mappings [ rp ] [ 'resource-manager' ] = {
"packageName" : ` azure-arm- ${ rp . toLowerCase ( ) } ` ,
"dir" : ` ${ rp } Management/lib ` ,
"source" : ` ${ rp } /resource-manager/readme.md `
}
newlyAdded . push ( ` ${ rp } ['resource-manager'] ` ) ;
console . log ( ` Updating RP: ${ rp } , "resource-manager". ` ) ;
console . dir ( mappings [ rp ] [ 'resource-manager' ] , { depth : null , colors : true } ) ;
}
if ( fs . existsSync ( dp ) && ! mappings [ rp ] [ 'data-plane' ] ) {
mappings [ rp ] [ 'data-plane' ] = {
"packageName" : ` azure- ${ rp . toLowerCase ( ) } ` ,
"dir" : ` ${ rp } /lib ` ,
"source" : ` ${ rp } /data-plane/readme.md `
}
newlyAdded . push ( ` ${ rp } ['data-plane'] ` ) ;
console . log ( ` Updating RP: ${ rp } , "data-plane". ` ) ;
console . dir ( mappings [ rp ] [ 'data-plane' ] , { depth : null , colors : true } ) ;
}
}
}
if ( ! newlyAdded . length ) {
console . log ( '\n\n> Mappings in ./codegen_mappings.json are already in sync...' ) ;
} else {
console . log ( ` \n \n > Basic properties like "packageName", "dir" and "source" have been added to ` +
` the newly added projects " ${ newlyAdded . join ( ) } " in the mappings. \n \n > Please ensure that other properties ` +
` like: "ft", "clientName", etc. are correctly added as deemed necessary. \n \n > If the specs repo had multiple ` +
` specs in data-plane or resource-manager (for example: "datalake-analytics.data-plane" has "catalog" ` +
` and "job" in it), then please update the project mappings yourself. ` )
}
2017-09-26 01:43:58 +03:00
console . log ( ` \n \n >>>>> Total projects in the mappings before sync: ${ originalProjectCount } ` ) ;
console . log ( ` \n >>>>> Total projects in the mappings after sync: ${ Object . keys ( mappings ) . length } ` ) ;
2017-08-26 04:53:43 +03:00
fs . writeFileSync ( './codegen_mappings.json' , JSON . stringify ( mappings , null , 2 ) ) ;
2017-09-28 06:01:40 +03:00
} ) ;
// This task synchronizes the dependencies in package.json to the versions of relative service libraries inside lib/services directory.
// This should be done in the end to ensure that all the package dependencies have the correct version.
gulp . task ( 'sync-deps-rollup' , ( cb ) => {
let packagePaths = glob . sync ( path . join ( _ _dirname , './lib/services' , '/**/package.json' ) ) . filter ( ( packagePath ) => {
2017-09-29 21:41:15 +03:00
return packagePath . match ( regexForExcludedServices ) === null ;
2017-09-28 06:01:40 +03:00
} ) ;
//console.log(packagePaths);
console . log ( ` Total packages found under lib/services: ${ packagePaths . length } ` ) ;
let rollupPackage = require ( './package.json' ) ;
let rollupDependencies = rollupPackage . dependencies ;
rollupDependencies [ 'ms-rest' ] = '^2.2.2' ;
2017-09-29 19:54:18 +03:00
rollupDependencies [ 'ms-rest-azure' ] = '^2.3.4' ;
2017-09-28 06:01:40 +03:00
packagePaths . forEach ( ( packagePath ) => {
const package = require ( packagePath ) ;
//console.log(package);
let packageName = package . name ;
let packageVersion = package . version ;
rollupDependencies [ packageName ] = packageVersion ;
} ) ;
rollupPackage . dependencies = Object . keys ( rollupDependencies ) . sort ( ) . reduce ( ( r , k ) => ( r [ k ] = rollupDependencies [ k ] , r ) , { } ) ;
console . log ( ` Total number of dependencies in the rollup package: ${ Object . keys ( rollupPackage . dependencies ) . length } ` ) ;
fs . writeFileSync ( './package.json' , JSON . stringify ( rollupPackage , null , 2 ) , { 'encoding' : 'utf8' } ) ;
2017-08-26 04:53:43 +03:00
} ) ;