const di = require ( 'di' ) ; import { Package , PackageRef } from './Package' ; import { Injector } from './Injector' ; import { Processor } from './Processor' ; import { processorValidationPackage } from './legacyPackages/processorValidation' ; import { sortByDependency } from './util/dependency-sort' ; import { getInjectablesFactory } from './util/getInjectables' ; import { logFactory } from './util/log' ; param { Package [ ] } packages export class Dgeni { static Package = Package ; injector : Injector ; packages : Record < string , Package > | Package [ ] = { } ; processors : Processor [ ] ; stopOnProcessingError : boolean ; handlerMap : { [ key ] : Function [ ] ; } ; constructor ( packages = [ ] ) { if ( ! Array . isArray ( packages ) ) { throw new Error ( 'packages must be an array' ) ; } this . package ( processorValidationPackage ) ; packages . map ( p => this . package ( p ) ) ; } param package param dependencies return package ( pkg , dependencies = [ ] ) { if ( this . injector ) { throw new Error ( 'injector already configured - you cannot add a new package' ) ; } if ( typeof pkg === 'string' ) { pkg = new Package ( pkg , dependencies ) ; } if ( ! ( Package . isPackage ( pkg ) ) ) { throw new Error ( 'package must be an instance of Package' ) ; } if ( this . packages [ pkg . name ] ) { throw new Error ( 'The "' + pkg . name + '" package has already been loaded' ) ; } this . packages [ pkg . name ] = pkg ; pkg . namedDependencies = pkg . dependencies . map ( ( dependency ) => { if ( Package . isPackage ( dependency ) ) { if ( ! this . packages [ dependency . name ] ) { this . package ( dependency ) ; } return dependency . name ; } return dependency ; } ) ; return pkg ; } /**
     * Configure the injector using the loaded packages.
     *
     * The injector is assigned to the `injector` property on `this`, which is used by the
     * `generate()` method. Subsequent calls to this method will just return the same injector.
     *
     * This method is useful in unit testing services and processors as it gives an easy way to
     * get hold of an instance of a ready instantiated component without having to load in all
     * the potential dependencies manually:
     *
     * ```
     * const Dgeni = require('dgeni');
     *
     * function getInjector() {
     *   const dgeni = new Dgeni();
     *   dgeni.package('testPackage', [require('dgeni-packages/base')])
     *     .factory('templateEngine', function dummyTemplateEngine() {});
     *   return dgeni.configureInjector();
     * };
     *
     * describe('someService', function() {
     *   const someService;
     *   beforeEach(function() {
     *     const injector = getInjector();
     *     someService = injector.get('someService');
     *   });
     *
     *   it("should do something", function() {
     *     someService.doSomething();
     *     ...
     *   });
     * });
     * ```
     */ configureInjector ( ) { if ( ! this . injector ) { const packages = this . packages = sortByDependency ( this . packages , 'namedDependencies' ) ; this . stopOnProcessingError = true ; const dgeniModule = new di . Module ( ) . value ( 'dgeni' , this ) . factory ( 'log' , logFactory ) . factory ( 'getInjectables' , getInjectablesFactory ) ; const modules = packages . map ( pkg => pkg . module ) ; modules . unshift ( dgeniModule ) ; const injector = this . injector = new di . Injector ( modules ) ; packages . forEach ( ( pkg ) => pkg . configFns . forEach ( ( configFn ) => injector . invoke ( configFn ) ) ) ; const processorMap = { } ; this . handlerMap = { } ; packages . forEach ( ( pkg ) => { pkg . processors . forEach ( function ( processorName ) { const processor = injector . get ( processorName ) ; processor . name = processorName ; processor . $package = pkg . name ; if ( processor . $enabled !== false ) { processorMap [ processorName ] = processor ; } } ) ; for ( const eventName in pkg . handlers ) { const handlers = this . handlerMap [ eventName ] = ( this . handlerMap [ eventName ] || [ ] ) ; pkg . handlers [ eventName ] . forEach ( handlerName => handlers . push ( injector . get ( handlerName ) ) ) ; } } ) ; this . processors = sortByDependency ( processorMap , '$runAfter' , '$runBefore' ) ; } return this . injector ; } return { Promise } generate ( ) { const injector = this . configureInjector ( ) ; const log = injector . get ( 'log' ) ; let processingPromise = this . triggerEvent ( 'generationStart' ) ; const currentDocs = [ ] ; processingPromise = processingPromise . then ( ( ) => currentDocs ) ; this . processors . forEach ( processor => { processingPromise = processingPromise . then ( docs => this . runProcessor ( processor , docs ) ) ; } ) ; processingPromise . catch ( error => { log . error ( error . message ) ; if ( error . stack ) { log . debug ( error . stack ) ; } log . error ( 'Failed to process the docs' ) ; } ) ; return processingPromise . then ( docs => { this . triggerEvent ( 'generationEnd' ) ; return docs ; } ) ; } runProcessor ( processor , docs ) { const log = this . injector . get ( 'log' ) ; const promise = Promise . resolve ( docs ) ; if ( ! processor . $process ) { return promise ; } return promise . then ( ( ) => { log . info ( 'running processor:' , processor . name ) ; return this . triggerProcessorEvent ( 'processorStart' , processor , docs ) ; } ) . then ( docs => processor . $process ( docs ) || docs ) . then ( docs => this . triggerProcessorEvent ( 'processorEnd' , processor , docs ) ) . catch ( ( error ) => { error . message = 'Error running processor "' + processor . name + '":\n' + error . message ; if ( this . stopOnProcessingError ) { return Promise . reject ( error ) ; } else { log . error ( error . message ) ; } return docs ; } ) ; } param { string } eventName return { Promise } triggerEvent ( eventName , ... extras ) { const handlers = this . handlerMap [ eventName ] ; let handlersPromise = Promise . resolve ( ) ; const results = [ ] ; if ( handlers ) { handlers . forEach ( handler => { handlersPromise = handlersPromise . then ( ( ) => { const handlerPromise = Promise . resolve ( handler ( eventName , ... extras ) ) ; handlerPromise . then ( result => results . push ( result ) ) ; return handlerPromise ; } ) ; } ) ; } return handlersPromise . then ( ( ) => results ) ; } triggerProcessorEvent ( eventName , processor , docs ) { return this . triggerEvent ( eventName , processor , docs ) . then ( ( ) => docs ) ; } info ( ) { const injector = this . configureInjector ( ) ; const log = injector . get ( 'log' ) ; for ( const pkgName in this . packages ) { log . info ( pkgName , '[' + this . packages [ pkgName ] . dependencies . map ( function ( dep ) { return JSON . stringify ( ( dep as Package ) . name ) ; } ) . join ( ', ' ) + ']' ) ; } log . info ( '== Processors (processing order) ==' ) ; this . processors . forEach ( ( processor , index ) => { log . info ( ( index + 1 ) + ': ' + processor . name , processor . $process ? '' : '(abstract)' , ' from ' , processor . $package ) ; if ( processor . description ) { log . info ( '   ' , processor . description ) ; } } ) ; } }