import { exec } from "child_process" ; import * as fs from "fs" ; import { promisify } from "util" ; import * as winston from "winston" ; import { Lumberjack } from "@fluidframework/server-services-telemetry" ; const asyncExec = promisify ( exec ) ; const signalFileName = "dummy" ; export class NodeAllowList { constructor ( ) { } public async testSource ( source ) { return Promise . resolve ( true ) ; } } export class NodeCodeLoader { constructor ( private readonly packageDirectory , private readonly waitTimeoutMSec , private readonly allowList ) { } public async load < T > ( pkg ) { if ( await this . allowList . testSource ( pkg ) ) { let packageName = "" ; if ( typeof pkg . package === "string" ) { packageName = pkg . package ; } else { packageName = `${ pkg . package . name }@${ pkg . package . version }` ; } const codeEntrypoint = await this . installOrWaitForPackages ( packageName ) ; const entry = import ( codeEntrypoint ) ; return entry ; } else { Promise . reject ( new Error ( "Invalid Package" ) ) ; } } private async installOrWaitForPackages ( pkg ) { const dataStores = pkg . match ( /(.*)\/(.*)@(.*)/ ) ; if ( ! dataStores ) { return Promise . reject ( new Error ( "Invalid package" ) ) ; } const [ , scope , name ] = dataStores ; const packageDirectory = `${ this . packageDirectory }/${ pkg }` ; const signalPath = `${ packageDirectory }/${ signalFileName }` ; const codeEntrypoint = `${ packageDirectory }/node_modules/${ scope }/${ name }` ; if ( ! fs . existsSync ( packageDirectory ) ) { const dirs = pkg . split ( "/" ) ; if ( ! fs . existsSync ( `${ this . packageDirectory }/${ dirs [ 0 ] }` ) ) { fs . mkdirSync ( `${ this . packageDirectory }/${ dirs [ 0 ] }` ) ; } if ( ! fs . existsSync ( packageDirectory ) ) { fs . mkdirSync ( packageDirectory ) ; } fs . copyFileSync ( `${ this . packageDirectory }/package.json` , `${ packageDirectory }/package.json` ) ; fs . copyFileSync ( `${ this . packageDirectory }/.npmrc` , `${ packageDirectory }/.npmrc` ) ; await asyncExec ( `npm install ${ pkg }` , { cwd : packageDirectory } ) ; fs . closeSync ( fs . openSync ( signalPath , "w" ) ) ; winston . info ( `Installed ${ pkg } in ${ packageDirectory } directory` ) ; Lumberjack . info ( `Installed ${ pkg } in ${ packageDirectory } directory` ) ; return codeEntrypoint ; } else { await this . waitForPackageFiles ( packageDirectory , signalFileName , this . waitTimeoutMSec ) ; winston . info ( `Package ${ pkg } is already installed` ) ; Lumberjack . info ( `Package ${ pkg } is already installed` ) ; return codeEntrypoint ; } } private async waitForPackageFiles ( targetDirectory , fileName , waitTimeout ) { return new Promise ( ( resolve , reject ) => { const watcher = fs . watch ( targetDirectory , ( eventType , newFileName ) => { if ( eventType === "rename" && newFileName === fileName ) { clearTimeout ( waitTimer ) ; watcher . close ( ) ; resolve ( ) ; } } ) ; const waitTimer = setTimeout ( ( ) => { watcher . close ( ) ; clearTimeout ( waitTimer ) ; reject ( new Error ( `${ fileName } in ${ targetDirectory } was not generated within ${ waitTimeout } msecs` ) ) ; } , waitTimeout ) ; if ( fs . existsSync ( `${ targetDirectory }/${ fileName }` ) ) { clearTimeout ( waitTimer ) ; watcher . close ( ) ; resolve ( ) ; } } ) ; } }