2021-07-03 02:45:15 +00:00
|
|
|
import * as childProcess from 'child_process'
|
|
|
|
import {UniversalPath} from '../util'
|
|
|
|
import {Inject, Injectable, InjectParam} from '../di'
|
|
|
|
import {Application} from '../lifecycle/Application'
|
|
|
|
import {Logging} from '../service/Logging'
|
|
|
|
import {NodeModule, ExtolloAwareNodeModule} from './types'
|
|
|
|
import {PackageDiscovered} from './PackageDiscovered'
|
2022-01-27 01:37:54 +00:00
|
|
|
import {Bus} from './bus'
|
2021-07-03 02:45:15 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A helper class for discovering and interacting with
|
|
|
|
* NPM-style modules.
|
|
|
|
*/
|
|
|
|
@Injectable()
|
|
|
|
export class NodeModules {
|
|
|
|
@Inject()
|
|
|
|
protected readonly logging!: Logging
|
|
|
|
|
|
|
|
@Inject()
|
2022-01-27 01:37:54 +00:00
|
|
|
protected readonly bus!: Bus
|
2021-07-03 02:45:15 +00:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
@InjectParam(Application.NODE_MODULES_INJECTION)
|
|
|
|
protected readonly manager: string,
|
|
|
|
) { }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the NodeModule entry for the base application.
|
|
|
|
*/
|
|
|
|
async app(): Promise<NodeModule> {
|
|
|
|
return new Promise<NodeModule>((res, rej) => {
|
|
|
|
childProcess.exec(`${this.manager} ls --json`, (error, stdout) => {
|
|
|
|
if ( error ) {
|
|
|
|
return rej(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
res(JSON.parse(stdout)[0])
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the path to the node_modules folder for the base application.
|
|
|
|
*/
|
|
|
|
async root(): Promise<UniversalPath> {
|
|
|
|
return new Promise<UniversalPath>((res, rej) => {
|
|
|
|
childProcess.exec(`${this.manager} root`, (error, stdout) => {
|
|
|
|
if ( error ) {
|
|
|
|
return rej(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
res(new UniversalPath(stdout.trim()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterate over packages, recursively, starting with the base application's
|
|
|
|
* package.json and fire PackageDiscovered events for any that have a valid
|
|
|
|
* Extollo discovery entry.
|
|
|
|
*/
|
|
|
|
async discover(): Promise<void> {
|
|
|
|
const root = await this.root()
|
|
|
|
const module = await this.app()
|
|
|
|
return this.discoverRoot(root, module)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively discover child-packages from the node_modules root for the
|
|
|
|
* given module.
|
|
|
|
*
|
|
|
|
* Fires PackageDiscovered events for valid, discovery-enabled packages.
|
|
|
|
*
|
|
|
|
* @param root - the path to node_modules
|
|
|
|
* @param module - the module whose children we are discovering
|
2021-11-27 06:12:51 +00:00
|
|
|
* @param seen - used to prevent duplicate packages when recursing
|
2021-07-03 02:45:15 +00:00
|
|
|
* @protected
|
|
|
|
*/
|
2021-11-27 06:12:51 +00:00
|
|
|
protected async discoverRoot(root: UniversalPath, module: NodeModule, seen: string[] = []): Promise<void> {
|
2021-07-03 02:45:15 +00:00
|
|
|
for ( const key in module.dependencies ) {
|
|
|
|
if ( !Object.prototype.hasOwnProperty.call(module.dependencies, key) ) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
this.logging.verbose(`Auto-discovery considering package: ${key}`)
|
|
|
|
|
|
|
|
try {
|
|
|
|
const packageJson = root.concat(key, 'package.json')
|
|
|
|
this.logging.verbose(`Auto-discovery package path: ${packageJson}`)
|
|
|
|
if ( await packageJson.exists() ) {
|
|
|
|
const packageJsonString: string = await packageJson.read()
|
|
|
|
const packageJsonData: ExtolloAwareNodeModule = JSON.parse(packageJsonString)
|
|
|
|
if ( !packageJsonData?.extollo?.discover ) {
|
|
|
|
this.logging.debug(`Skipping non-discoverable package: ${key}`)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-11-27 06:12:51 +00:00
|
|
|
if ( seen.includes(key) ) {
|
|
|
|
this.logging.debug(`Skipping already-discovered package: ${key}`)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-07-03 02:45:15 +00:00
|
|
|
this.logging.info(`Auto-discovering package: ${key}`)
|
2021-11-27 06:12:51 +00:00
|
|
|
seen.push(key)
|
2022-01-27 01:37:54 +00:00
|
|
|
await this.bus.push(new PackageDiscovered(packageJsonData, packageJson.clone()))
|
2021-07-03 02:45:15 +00:00
|
|
|
|
|
|
|
const packageNodeModules = packageJson.concat('..', 'node_modules')
|
|
|
|
if ( await packageNodeModules.exists() && packageJsonData?.extollo?.recursiveDependencies?.discover ) {
|
|
|
|
this.logging.debug(`Recursing: ${packageNodeModules}`)
|
2021-11-27 06:12:51 +00:00
|
|
|
await this.discoverRoot(packageNodeModules, packageJsonData, seen)
|
2021-07-03 02:45:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e: unknown) {
|
|
|
|
this.logging.error(`Encountered error while discovering package: ${key}`)
|
|
|
|
this.logging.error(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|