adapt-authoring-core/lib/App.js

import AbstractModule from './AbstractModule.js'
import DependencyLoader from './DependencyLoader.js'
import { fileURLToPath } from 'url'
import fs from 'fs'
import path from 'path'
import Utils from './Utils.js'

let instance
/**
 * Core functionality
 * @namespace core
 */
/**
 * The main application class
 * @memberof core
 * @extends {AbstractModule}
 */
class App extends AbstractModule {
  /**
   * The singleton instance. Self-initialises it if there isn't one.
   * @type {App}
   */
  static get instance () {
    if (!instance) instance = new App()
    return instance
  }

  /** @override */
  constructor () {
    const rootDir = process.env.ROOT_DIR ?? process.cwd()
    const adaptJson = JSON.parse(fs.readFileSync(path.join(rootDir, Utils.metadataFileName)))
    const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, Utils.packageFileName)))
    super(null, { ...packageJson, ...adaptJson, name: 'adapt-authoring-core', rootDir })
  }

  /** @override */
  async init () {
    /**
     * Reference to the passed arguments (parsed for easy reference)
     * @type {Object}
     */
    this.args = Utils.getArgs()
    /**
     * Instance of App instance (required by all AbstractModules)
     * @type {App}
     */
    this.app = this
    /**
     * Reference to the DependencyLoader instance
     * @type {DependencyLoader}
     */
    this.dependencyloader = new DependencyLoader(this)

    /** @ignore */ this._isStarting = false

    const configRootDir = this.getConfig('rootDir')
    if (configRootDir) /** @ignore */this.rootDir = configRootDir

    let startError
    try {
      await this.start()
    } catch (e) {
      startError = e
    }
    const failedMods = this.dependencyloader.failedModules
    if (failedMods.length) this.log('warn', `${failedMods.length} module${failedMods.length === 1 ? '' : 's'} failed to load: ${failedMods}. See above for details`)
    if (startError) {
      process.exitCode = 1
      throw new Error('Failed to start App')
    }
  }

  /**
   * The Adapt module dependencies and their configs
   * @type {Object}
   */
  get dependencies () {
    return this.dependencyloader.configs
  }

  /**
   * Starts the app
   * @return {Promise} Resolves when the app has started
   */
  async start () {
    if (this._isReady) throw new Error('warn', 'cannot start app, already started')
    if (this._isStarting) throw new Error('warn', 'cannot start app, already initialising')

    this._isStarting = true

    await this.dependencyloader.load()

    this._isStarting = false
  }

  /**
   * Enables waiting for other modules to load
   * @param {...String} modNames Names of modules to wait for
   * @return {Promise} Resolves when specified module has been loaded
   */
  async waitForModule (...modNames) {
    const results = await Promise.all(modNames.map(m => this.dependencyloader.waitForModule(m)))
    return results.length > 1 ? results : results[0]
  }

  /** @override */
  setReady (error) {
    this._isStarting = false
    super.setReady(error)
  }
}

export default App