import { exec } from "child_process" ; import * as fs from "fs" ; import { promisify } from "util" ; const asyncExec = promisify ( exec ) ; const signalFileName = "dummy" ; /**
 * Installs a versioned npm package to the desired directory on a local file system. Once installed, it returns
 * the code through the entry point. To guard against concurrent writes, the class uses a sentinel file approach
 * to guard against concurrent writes.
 */ export class NodeCodeLoader { constructor ( private readonly packageDirectory , private readonly waitTimeoutMSec , private readonly useLocalDirectory = true ) { } public async load < T > ( 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 ; } private async installOrWaitForPackages ( pkg ) { const fluidObjects = pkg . match ( /(.*)\/(.*)@(.*)/ ) ; if ( fluidObjects === null ) { return Promise . reject ( new Error ( "Invalid package" ) ) ; } const [ , scope , name ] = fluidObjects ; const packageDirectory = `${ this . packageDirectory }/${ pkg }` ; const signalPath = `${ packageDirectory }/${ signalFileName }` ; const codeEntrypoint = `${ packageDirectory }/node_modules/${ scope }/${ name }` ; if ( ! fs . existsSync ( packageDirectory ) ) { fs . mkdirSync ( packageDirectory , { recursive : true } ) ; if ( this . useLocalDirectory ) { if ( fs . existsSync ( `${ __dirname }/../.npmrc` ) ) { fs . copyFileSync ( `${ __dirname }/../.npmrc` , `${ packageDirectory }/.npmrc` ) ; } } else if ( fs . existsSync ( `${ this . packageDirectory }/.npmrc` ) ) { fs . copyFileSync ( `${ this . packageDirectory }/.npmrc` , `${ packageDirectory }/.npmrc` ) ; } await asyncExec ( `npm install ${ pkg }` , { cwd : packageDirectory } ) ; fs . closeSync ( fs . openSync ( signalPath , "w" ) ) ; return codeEntrypoint ; } else { await this . waitForPackageFiles ( packageDirectory , signalFileName , this . waitTimeoutMSec ) ; 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 ( ) ; } } ) ; } }