text: // from package.js
  const path = require('path');
  const asyncEach = require('async/each');
  const CSON = require('season');
  const fs = require('fs-plus');
  const { Emitter, CompositeDisposable } = require('event-kit');
  const dedent = require('dedent');
  
  const CompileCache = require('./compile-cache');
  const ModuleCache = require('./module-cache');
  const BufferedProcess = require('./buffered-process');
  const { requireModule } = require('./module-utils');
  
  module.exports = class AbstractPackage {
    constructor(params) {
      if (this.constructor === AbstractPackage) {
        throw new Error("Abstract package cannot be directly instantiated");
      }
  
      this.config = params.config;
      this.packageManager = params.packageManager;
      this.styleManager = params.styleManager;
      this.commandRegistry = params.commandRegistry;
      this.keymapManager = params.keymapManager;
      this.notificationManager = params.notificationManager;
      this.grammarRegistry = params.grammarRegistry;
      this.themeManager = params.themeManager;
      this.menuManager = params.menuManager;
      this.contextMenuManager = params.contextMenuManager;
      this.deserializerManager = params.deserializerManager;
      this.viewRegistry = params.viewRegistry;
      this.emitter = new Emitter();
  
      this.mainModule = null;
      this.path = params.path;
      this.preloadedPackage = params.preloadedPackage;
      this.metadata =
        params.metadata || this.packageManager.loadPackageMetadata(this.path);
      this.bundledPackage =
        params.bundledPackage != null
          ? params.bundledPackage
          : this.packageManager.isBundledPackagePath(this.path);
      this.name =
        (this.metadata && this.metadata.name) ||
        params.name ||
        path.basename(this.path);
      this.reset();
    }
  
    getType() {
      throw new Error('Method getType() needs to be implemented in derivative classes');
      
    }; 
  
    getStyleSheetPriority() {
      throw new Error('Method getStyleSheetPriority() needs to be implemented in derivative classes');
  
    };
  
    enable() {
      throw new Error('Method enable() needs to be implemented in derivative classes');
  
    };
  
    disable() {
      throw new Error('Method disable() needs to be implemented in derivative classes');
  
    };
  
    preload() {
      throw new Error('Method preload() needs to be implemented in derivative classes');
    };
  
    finishloading() {
      throw new Error('Method finishloading() needs to be implemented in derivative classes');
  
    };
  
    load() {
      throw new Error('Method load() needs to be implemented in derivative classes');
  
    };
  
    activate() {
      throw new Error('Method activate() needs to be implemented in derivative classes');
  
    };
  
    /*
    Section: Event Subscription
    */
  
    // Essential: Invoke the given callback when all packages have been activated.
    //
    // * `callback` {Function}
    //
    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
    onDidDeactivate(callback) {
      return this.emitter.on('did-deactivate', callback);
    }
  
    /*
    Section: Instance Methods
    */
  
  
    isTheme() {
      return this.metadata && this.metadata.theme;
    }
  
    measureLoadTime(key, fn) {
      const startTime = window.performance.now();
      const value = fn();
      this[key] = Math.round(window.performance.now() - startTime);
      return value;
    }
  
    getType() {
      return 'atom';
    }
  
    unload() {
      this.unregisterTranspilerConfig();
    }
  
    shouldRequireMainModuleOnLoad() {
      return !(
        this.metadata.deserializers ||
        this.metadata.viewProviders ||
        this.metadata.configSchema ||
        this.activationShouldBeDeferred() ||
        localStorage.getItem(this.getCanDeferMainModuleRequireStorageKey()) ===
          'true'
      );
    }
  
    reset() {
      this.stylesheets = [];
      this.keymaps = [];
      this.menus = [];
      this.grammars = [];
      this.settings = [];
      this.mainInitialized = false;
      this.mainActivated = false;
      this.deserialized = false;
    }
  
    initializeIfNeeded() {
      if (this.mainInitialized) return;
      this.measureLoadTime('initializeTime', () => {
        try {
          // The main module's `initialize()` method is guaranteed to be called
          // before its `activate()`. This gives you a chance to handle the
          // serialized package state before the package's derserializers and view
          // providers are used.
          if (!this.mainModule) this.requireMainModule();
          if (typeof this.mainModule.initialize === 'function') {
            this.mainModule.initialize(
              this.packageManager.getPackageState(this.name) || {}
            );
          }
          this.mainInitialized = true;
        } catch (error) {
          this.handleError(
            `Failed to initialize the ${this.name} package`,
            error
          );
        }
      });
    }
  
    activateNow() {
      try {
        if (!this.mainModule) this.requireMainModule();
        this.configSchemaRegisteredOnActivate = this.registerConfigSchemaFromMainModule();
        this.registerViewProviders();
        this.activateStylesheets();
        if (this.mainModule && !this.mainActivated) {
          this.initializeIfNeeded();
          if (typeof this.mainModule.activateConfig === 'function') {
            this.mainModule.activateConfig();
          }
          if (typeof this.mainModule.activate === 'function') {
            this.mainModule.activate(
              this.packageManager.getPackageState(this.name) || {}
            );
          }
          this.mainActivated = true;
          this.activateServices();
        }
        if (this.activationCommandSubscriptions)
          this.activationCommandSubscriptions.dispose();
        if (this.activationHookSubscriptions)
          this.activationHookSubscriptions.dispose();
        if (this.workspaceOpenerSubscriptions)
          this.workspaceOpenerSubscriptions.dispose();
      } catch (error) {
        this.handleError(`Failed to activate the ${this.name} package`, error);
      }
  
      if (typeof this.resolveActivationPromise === 'function')
        this.resolveActivationPromise();
    }
  
    registerConfigSchemaFromMetadata() {
      const configSchema = this.metadata.configSchema;
      if (configSchema) {
        this.config.setSchema(this.name, {
          type: 'object',
          properties: configSchema
        });
        return true;
      } else {
        return false;
      }
    }
  
    registerConfigSchemaFromMainModule() {
      if (this.mainModule && !this.configSchemaRegisteredOnLoad) {
        if (typeof this.mainModule.config === 'object') {
          this.config.setSchema(this.name, {
            type: 'object',
            properties: this.mainModule.config
          });
          return true;
        }
      }
      return false;
    }
  
    // TODO: Remove. Settings view calls this method currently.
    activateConfig() {
      if (this.configSchemaRegisteredOnLoad) return;
      this.requireMainModule();
      this.registerConfigSchemaFromMainModule();
    }
  
    activateStylesheets() {
      if (this.stylesheetsActivated) return;
  
      this.stylesheetDisposables = new CompositeDisposable();
  
      const priority = this.getStyleSheetPriority();
      for (let [sourcePath, source] of this.stylesheets) {
        const match = path.basename(sourcePath).match(/[^.]*\.([^.]*)\./);
  
        let context;
        if (match) {
          context = match[1];
        } else if (this.metadata.theme === 'syntax') {
          context = 'atom-text-editor';
        }
  
        this.stylesheetDisposables.add(
          this.styleManager.addStyleSheet(source, {
            sourcePath,
            priority,
            context,
            skipDeprecatedSelectorsTransformation: this.bundledPackage
          })
        );
      }
  
      this.stylesheetsActivated = true;
    }
  
    activateResources() {
      if (!this.activationDisposables)
        this.activationDisposables = new CompositeDisposable();
  
      const packagesWithKeymapsDisabled = this.config.get(
        'core.packagesWithKeymapsDisabled'
      );
      if (
        packagesWithKeymapsDisabled &&
        packagesWithKeymapsDisabled.includes(this.name)
      ) {
        this.deactivateKeymaps();
      } else if (!this.keymapActivated) {
        this.activateKeymaps();
      }
  
      if (!this.menusActivated) {
        this.activateMenus();
      }
  
      if (!this.grammarsActivated) {
        for (let grammar of this.grammars) {
          grammar.activate();
        }
        this.grammarsActivated = true;
      }
  
      if (!this.settingsActivated) {
        for (let settings of this.settings) {
          settings.activate(this.config);
        }
        this.settingsActivated = true;
      }
    }
  
    activateKeymaps() {
      if (this.keymapActivated) return;
  
      this.keymapDisposables = new CompositeDisposable();
  
      const validateSelectors = !this.preloadedPackage;
      for (let [keymapPath, map] of this.keymaps) {
        this.keymapDisposables.add(
          this.keymapManager.add(keymapPath, map, 0, validateSelectors)
        );
      }
      this.menuManager.update();
  
      this.keymapActivated = true;
    }
  
    deactivateKeymaps() {
      if (!this.keymapActivated) return;
      if (this.keymapDisposables) {
        this.keymapDisposables.dispose();
      }
      this.menuManager.update();
      this.keymapActivated = false;
    }
  
    hasKeymaps() {
      for (let [, map] of this.keymaps) {
        if (map.length > 0) return true;
      }
      return false;
    }
  
    activateMenus() {
      const validateSelectors = !this.preloadedPackage;
      for (const [menuPath, map] of this.menus) {
        if (map['context-menu']) {
          try {
            const itemsBySelector = map['context-menu'];
            this.activationDisposables.add(
              this.contextMenuManager.add(itemsBySelector, validateSelectors)
            );
          } catch (error) {
            if (error.code === 'EBADSELECTOR') {
              error.message += ` in ${menuPath}`;
              error.stack += `\n  at ${menuPath}:1:1`;
            }
            throw error;
          }
        }
      }
  
      for (const [, map] of this.menus) {
        if (map.menu)
          this.activationDisposables.add(this.menuManager.add(map.menu));
      }
  
      this.menusActivated = true;
    }
  
    activateServices() {
      let methodName, version, versions;
      for (var name in this.metadata.providedServices) {
        ({ versions } = this.metadata.providedServices[name]);
        const servicesByVersion = {};
        for (version in versions) {
          methodName = versions[version];
          if (typeof this.mainModule[methodName] === 'function') {
            servicesByVersion[version] = this.mainModule[methodName]();
          }
        }
        this.activationDisposables.add(
          this.packageManager.serviceHub.provide(name, servicesByVersion)
        );
      }
  
      for (name in this.metadata.consumedServices) {
        ({ versions } = this.metadata.consumedServices[name]);
        for (version in versions) {
          methodName = versions[version];
          if (typeof this.mainModule[methodName] === 'function') {
            this.activationDisposables.add(
              this.packageManager.serviceHub.consume(
                name,
                version,
                this.mainModule[methodName].bind(this.mainModule)
              )
            );
          }
        }
      }
    }
  
    registerURIHandler() {
      const handlerConfig = this.getURIHandler();
      const methodName = handlerConfig && handlerConfig.method;
      if (methodName) {
        this.uriHandlerSubscription = this.packageManager.registerURIHandlerForPackage(
          this.name,
          (...args) => this.handleURI(methodName, args)
        );
      }
    }
  
    unregisterURIHandler() {
      if (this.uriHandlerSubscription) this.uriHandlerSubscription.dispose();
    }
  
    handleURI(methodName, args) {
      this.activate().then(() => {
        if (this.mainModule[methodName])
          this.mainModule[methodName].apply(this.mainModule, args);
      });
      if (!this.mainActivated) this.activateNow();
    }
  
    registerTranspilerConfig() {
      if (this.metadata.atomTranspilers) {
        CompileCache.addTranspilerConfigForPath(
          this.path,
          this.name,
          this.metadata,
          this.metadata.atomTranspilers
        );
      }
    }
  
    unregisterTranspilerConfig() {
      if (this.metadata.atomTranspilers) {
        CompileCache.removeTranspilerConfigForPath(this.path);
      }
    }
  
    loadKeymaps() {
      if (this.bundledPackage && this.packageManager.packagesCache[this.name]) {
        this.keymaps = [];
        for (const keymapPath in this.packageManager.packagesCache[this.name]
          .keymaps) {
          const keymapObject = this.packageManager.packagesCache[this.name]
            .keymaps[keymapPath];
          this.keymaps.push([`core:${keymapPath}`, keymapObject]);
        }
      } else {
        this.keymaps = this.getKeymapPaths().map(keymapPath => [
          keymapPath,
          CSON.readFileSync(keymapPath, { allowDuplicateKeys: false }) || {}
        ]);
      }
    }
  
    loadMenus() {
      if (this.bundledPackage && this.packageManager.packagesCache[this.name]) {
        this.menus = [];
        for (const menuPath in this.packageManager.packagesCache[this.name]
          .menus) {
          const menuObject = this.packageManager.packagesCache[this.name].menus[
            menuPath
          ];
          this.menus.push([`core:${menuPath}`, menuObject]);
        }
      } else {
        this.menus = this.getMenuPaths().map(menuPath => [
          menuPath,
          CSON.readFileSync(menuPath) || {}
        ]);
      }
    }
  
    getKeymapPaths() {
      const keymapsDirPath = path.join(this.path, 'keymaps');
      if (this.metadata.keymaps) {
        return this.metadata.keymaps.map(name =>
          fs.resolve(keymapsDirPath, name, ['json', 'cson', ''])
        );
      } else {
        return fs.listSync(keymapsDirPath, ['cson', 'json']);
      }
    }
  
    getMenuPaths() {
      const menusDirPath = path.join(this.path, 'menus');
      if (this.metadata.menus) {
        return this.metadata.menus.map(name =>
          fs.resolve(menusDirPath, name, ['json', 'cson', ''])
        );
      } else {
        return fs.listSync(menusDirPath, ['cson', 'json']);
      }
    }
  
    loadStylesheets() {
      this.stylesheets = this.getStylesheetPaths().map(stylesheetPath => [
        stylesheetPath,
        this.themeManager.loadStylesheet(stylesheetPath, true)
      ]);
    }
  
    registerDeserializerMethods() {
      if (this.metadata.deserializers) {
        Object.keys(this.metadata.deserializers).forEach(deserializerName => {
          const methodName = this.metadata.deserializers[deserializerName];
          this.deserializerManager.add({
            name: deserializerName,
            deserialize: (state, atomEnvironment) => {
              this.registerViewProviders();
              this.requireMainModule();
              this.initializeIfNeeded();
              if (atomEnvironment.packages.hasActivatedInitialPackages()) {
                // Only explicitly activate the package if initial packages
                // have finished activating. This is because deserialization
                // generally occurs at Atom startup, which happens before the
                // workspace element is added to the DOM and is inconsistent with
                // with when initial package activation occurs. Triggering activation
                // immediately may cause problems with packages that expect to
                // always have access to the workspace element.
                // Otherwise, we just set the deserialized flag and package-manager
                // will activate this package as normal during initial package activation.
                this.activateNow();
              }
              this.deserialized = true;
              return this.mainModule[methodName](state, atomEnvironment);
            }
          });
        });
      }
    }
  
    activateCoreStartupServices() {
      const directoryProviderService =
        this.metadata.providedServices &&
        this.metadata.providedServices['atom.directory-provider'];
      if (directoryProviderService) {
        this.requireMainModule();
        const servicesByVersion = {};
        for (let version in directoryProviderService.versions) {
          const methodName = directoryProviderService.versions[version];
          if (typeof this.mainModule[methodName] === 'function') {
            servicesByVersion[version] = this.mainModule[methodName]();
          }
        }
        this.packageManager.serviceHub.provide(
          'atom.directory-provider',
          servicesByVersion
        );
      }
    }
  
    registerViewProviders() {
      if (this.metadata.viewProviders && !this.registeredViewProviders) {
        this.requireMainModule();
        this.metadata.viewProviders.forEach(methodName => {
          this.viewRegistry.addViewProvider(model => {
            this.initializeIfNeeded();
            return this.mainModule[methodName](model);
          });
        });
        this.registeredViewProviders = true;
      }
    }
  
    getStylesheetsPath() {
      return path.join(this.path, 'styles');
    }
  
    getStylesheetPaths() {
      if (
        this.bundledPackage &&
        this.packageManager.packagesCache[this.name] &&
        this.packageManager.packagesCache[this.name].styleSheetPaths
      ) {
        const { styleSheetPaths } = this.packageManager.packagesCache[this.name];
        return styleSheetPaths.map(styleSheetPath =>
          path.join(this.path, styleSheetPath)
        );
      } else {
        let indexStylesheet;
        const stylesheetDirPath = this.getStylesheetsPath();
        if (this.metadata.mainStyleSheet) {
          return [fs.resolve(this.path, this.metadata.mainStyleSheet)];
        } else if (this.metadata.styleSheets) {
          return this.metadata.styleSheets.map(name =>
            fs.resolve(stylesheetDirPath, name, ['css', 'less', ''])
          );
        } else if (
          (indexStylesheet = fs.resolve(this.path, 'index', ['css', 'less']))
        ) {
          return [indexStylesheet];
        } else {
          return fs.listSync(stylesheetDirPath, ['css', 'less']);
        }
      }
    }
  
    loadGrammarsSync() {
      if (this.grammarsLoaded) return;
  
      let grammarPaths;
      if (this.preloadedPackage && this.packageManager.packagesCache[this.name]) {
        ({ grammarPaths } = this.packageManager.packagesCache[this.name]);
      } else {
        grammarPaths = fs.listSync(path.join(this.path, 'grammars'), [
          'json',
          'cson'
        ]);
      }
  
      for (let grammarPath of grammarPaths) {
        if (
          this.preloadedPackage &&
          this.packageManager.packagesCache[this.name]
        ) {
          grammarPath = path.resolve(
            this.packageManager.resourcePath,
            grammarPath
          );
        }
  
        try {
          const grammar = this.grammarRegistry.readGrammarSync(grammarPath);
          grammar.packageName = this.name;
          grammar.bundledPackage = this.bundledPackage;
          this.grammars.push(grammar);
          grammar.activate();
        } catch (error) {
          console.warn(
            `Failed to load grammar: ${grammarPath}`,
            error.stack || error
          );
        }
      }
  
      this.grammarsLoaded = true;
      this.grammarsActivated = true;
    }
  
    loadGrammars() {
      if (this.grammarsLoaded) return Promise.resolve();
  
      const loadGrammar = (grammarPath, callback) => {
        if (this.preloadedPackage) {
          grammarPath = path.resolve(
            this.packageManager.resourcePath,
            grammarPath
          );
        }
  
        return this.grammarRegistry.readGrammar(grammarPath, (error, grammar) => {
          if (error) {
            const detail = `${error.message} in ${grammarPath}`;
            const stack = `${error.stack}\n  at ${grammarPath}:1:1`;
            this.notificationManager.addFatalError(
              `Failed to load a ${this.name} package grammar`,
              { stack, detail, packageName: this.name, dismissable: true }
            );
          } else {
            grammar.packageName = this.name;
            grammar.bundledPackage = this.bundledPackage;
            this.grammars.push(grammar);
            if (this.grammarsActivated) grammar.activate();
          }
          return callback();
        });
      };
  
      return new Promise(resolve => {
        if (
          this.preloadedPackage &&
          this.packageManager.packagesCache[this.name]
        ) {
          const { grammarPaths } = this.packageManager.packagesCache[this.name];
          return asyncEach(grammarPaths, loadGrammar, () => resolve());
        } else {
          const grammarsDirPath = path.join(this.path, 'grammars');
          fs.exists(grammarsDirPath, grammarsDirExists => {
            if (!grammarsDirExists) return resolve();
            fs.list(grammarsDirPath, ['json', 'cson'], (error, grammarPaths) => {
              if (error || !grammarPaths) return resolve();
              asyncEach(grammarPaths, loadGrammar, () => resolve());
            });
          });
        }
      });
    }
  
    loadSettings() {
      this.settings = [];
  
      const loadSettingsFile = (settingsPath, callback) => {
        return SettingsFile.load(settingsPath, (error, settingsFile) => {
          if (error) {
            const detail = `${error.message} in ${settingsPath}`;
            const stack = `${error.stack}\n  at ${settingsPath}:1:1`;
            this.notificationManager.addFatalError(
              `Failed to load the ${this.name} package settings`,
              { stack, detail, packageName: this.name, dismissable: true }
            );
          } else {
            this.settings.push(settingsFile);
            if (this.settingsActivated) settingsFile.activate(this.config);
          }
          return callback();
        });
      };
  
      if (this.preloadedPackage && this.packageManager.packagesCache[this.name]) {
        for (let settingsPath in this.packageManager.packagesCache[this.name]
          .settings) {
          const properties = this.packageManager.packagesCache[this.name]
            .settings[settingsPath];
          const settingsFile = new SettingsFile(
            `core:${settingsPath}`,
            properties || {}
          );
          this.settings.push(settingsFile);
          if (this.settingsActivated) settingsFile.activate(this.config);
        }
      } else {
        return new Promise(resolve => {
          const settingsDirPath = path.join(this.path, 'settings');
          fs.exists(settingsDirPath, settingsDirExists => {
            if (!settingsDirExists) return resolve();
            fs.list(settingsDirPath, ['json', 'cson'], (error, settingsPaths) => {
              if (error || !settingsPaths) return resolve();
              asyncEach(settingsPaths, loadSettingsFile, () => resolve());
            });
          });
        });
      }
    }
  
    serialize() {
      if (this.mainActivated) {
        if (typeof this.mainModule.serialize === 'function') {
          try {
            return this.mainModule.serialize();
          } catch (error) {
            console.error(
              `Error serializing package '${this.name}'`,
              error.stack
            );
          }
        }
      }
    }
  
    async deactivate() {
      this.activationPromise = null;
      this.resolveActivationPromise = null;
      if (this.activationCommandSubscriptions)
        this.activationCommandSubscriptions.dispose();
      if (this.activationHookSubscriptions)
        this.activationHookSubscriptions.dispose();
      this.configSchemaRegisteredOnActivate = false;
      this.unregisterURIHandler();
      this.deactivateResources();
      this.deactivateKeymaps();
  
      if (!this.mainActivated) {
        this.emitter.emit('did-deactivate');
        return;
      }
  
      if (typeof this.mainModule.deactivate === 'function') {
        try {
          const deactivationResult = this.mainModule.deactivate();
          if (
            deactivationResult &&
            typeof deactivationResult.then === 'function'
          ) {
            await deactivationResult;
          }
        } catch (error) {
          console.error(`Error deactivating package '${this.name}'`, error.stack);
        }
      }
  
      if (typeof this.mainModule.deactivateConfig === 'function') {
        try {
          await this.mainModule.deactivateConfig();
        } catch (error) {
          console.error(`Error deactivating package '${this.name}'`, error.stack);
        }
      }
  
      this.mainActivated = false;
      this.mainInitialized = false;
      this.emitter.emit('did-deactivate');
    }
  
    deactivateResources() {
      for (let grammar of this.grammars) {
        grammar.deactivate();
      }
      for (let settings of this.settings) {
        settings.deactivate(this.config);
      }
  
      if (this.stylesheetDisposables) this.stylesheetDisposables.dispose();
      if (this.activationDisposables) this.activationDisposables.dispose();
      if (this.keymapDisposables) this.keymapDisposables.dispose();
  
      this.stylesheetsActivated = false;
      this.grammarsActivated = false;
      this.settingsActivated = false;
      this.menusActivated = false;
    }
  
    reloadStylesheets() {
      try {
        this.loadStylesheets();
      } catch (error) {
        this.handleError(
          `Failed to reload the ${this.name} package stylesheets`,
          error
        );
      }
  
      if (this.stylesheetDisposables) this.stylesheetDisposables.dispose();
      this.stylesheetDisposables = new CompositeDisposable();
      this.stylesheetsActivated = false;
      this.activateStylesheets();
    }
  
    requireMainModule() {
      if (this.bundledPackage && this.packageManager.packagesCache[this.name]) {
        if (this.packageManager.packagesCache[this.name].main) {
          this.mainModule = requireModule(
            this.packageManager.packagesCache[this.name].main
          );
          return this.mainModule;
        }
      } else if (this.mainModuleRequired) {
        return this.mainModule;
      } else if (!this.isCompatible()) {
        const nativeModuleNames = this.incompatibleModules
          .map(m => m.name)
          .join(', ');
        console.warn(dedent`
          Failed to require the main module of '${
            this.name
          }' because it requires one or more incompatible native modules (${nativeModuleNames}).
          Run \`apm rebuild\` in the package directory and restart Atom to resolve.\
        `);
      } else {
        const mainModulePath = this.getMainModulePath();
        if (fs.isFileSync(mainModulePath)) {
          this.mainModuleRequired = true;
  
          const previousViewProviderCount = this.viewRegistry.getViewProviderCount();
          const previousDeserializerCount = this.deserializerManager.getDeserializerCount();
          this.mainModule = requireModule(mainModulePath);
          if (
            this.viewRegistry.getViewProviderCount() ===
              previousViewProviderCount &&
            this.deserializerManager.getDeserializerCount() ===
              previousDeserializerCount
          ) {
            localStorage.setItem(
              this.getCanDeferMainModuleRequireStorageKey(),
              'true'
            );
          } else {
            localStorage.removeItem(
              this.getCanDeferMainModuleRequireStorageKey()
            );
          }
          return this.mainModule;
        }
      }
    }
  
    getMainModulePath() {
      if (this.resolvedMainModulePath) return this.mainModulePath;
      this.resolvedMainModulePath = true;
  
      if (this.bundledPackage && this.packageManager.packagesCache[this.name]) {
        if (this.packageManager.packagesCache[this.name].main) {
          this.mainModulePath = path.resolve(
            this.packageManager.resourcePath,
            'static',
            this.packageManager.packagesCache[this.name].main
          );
        } else {
          this.mainModulePath = null;
        }
      } else {
        const mainModulePath = this.metadata.main
          ? path.join(this.path, this.metadata.main)
          : path.join(this.path, 'index');
        this.mainModulePath = fs.resolveExtension(mainModulePath, [
          '',
          ...CompileCache.supportedExtensions
        ]);
      }
      return this.mainModulePath;
    }
  
    activationShouldBeDeferred() {
      return (
        !this.deserialized &&
        (this.hasActivationCommands() ||
          this.hasActivationHooks() ||
          this.hasWorkspaceOpeners() ||
          this.hasDeferredURIHandler())
      );
    }
  
    hasActivationHooks() {
      const hooks = this.getActivationHooks();
      return hooks && hooks.length > 0;
    }
  
    hasWorkspaceOpeners() {
      const openers = this.getWorkspaceOpeners();
      return openers && openers.length > 0;
    }
  
    hasActivationCommands() {
      const object = this.getActivationCommands();
      for (let selector in object) {
        const commands = object[selector];
        if (commands.length > 0) return true;
      }
      return false;
    }
  
    hasDeferredURIHandler() {
      const handler = this.getURIHandler();
      return handler && handler.deferActivation !== false;
    }
  
    subscribeToDeferredActivation() {
      this.subscribeToActivationCommands();
      this.subscribeToActivationHooks();
      this.subscribeToWorkspaceOpeners();
    }
  
    subscribeToActivationCommands() {
      this.activationCommandSubscriptions = new CompositeDisposable();
      const object = this.getActivationCommands();
      for (let selector in object) {
        const commands = object[selector];
        for (let command of commands) {
          ((selector, command) => {
            // Add dummy command so it appears in menu.
            // The real command will be registered on package activation
            try {
              this.activationCommandSubscriptions.add(
                this.commandRegistry.add(selector, command, function() {})
              );
            } catch (error) {
              if (error.code === 'EBADSELECTOR') {
                const metadataPath = path.join(this.path, 'package.json');
                error.message += ` in ${metadataPath}`;
                error.stack += `\n  at ${metadataPath}:1:1`;
              }
              throw error;
            }
  
            this.activationCommandSubscriptions.add(
              this.commandRegistry.onWillDispatch(event => {
                if (event.type !== command) return;
                let currentTarget = event.target;
                while (currentTarget) {
                  if (currentTarget.webkitMatchesSelector(selector)) {
                    this.activationCommandSubscriptions.dispose();
                    this.activateNow();
                    break;
                  }
                  currentTarget = currentTarget.parentElement;
                }
              })
            );
          })(selector, command);
        }
      }
    }
  
    getActivationCommands() {
      if (this.activationCommands) return this.activationCommands;
  
      this.activationCommands = {};
  
      if (this.metadata.activationCommands) {
        for (let selector in this.metadata.activationCommands) {
          const commands = this.metadata.activationCommands[selector];
          if (!this.activationCommands[selector])
            this.activationCommands[selector] = [];
          if (typeof commands === 'string') {
            this.activationCommands[selector].push(commands);
          } else if (Array.isArray(commands)) {
            this.activationCommands[selector].push(...commands);
          }
        }
      }
  
      return this.activationCommands;
    }
  
    subscribeToActivationHooks() {
      this.activationHookSubscriptions = new CompositeDisposable();
      for (let hook of this.getActivationHooks()) {
        if (typeof hook === 'string' && hook.trim().length > 0) {
          this.activationHookSubscriptions.add(
            this.packageManager.onDidTriggerActivationHook(hook, () =>
              this.activateNow()
            )
          );
        }
      }
    }
  
    getActivationHooks() {
      if (this.metadata && this.activationHooks) return this.activationHooks;
  
      if (this.metadata.activationHooks) {
        if (Array.isArray(this.metadata.activationHooks)) {
          this.activationHooks = Array.from(
            new Set(this.metadata.activationHooks)
          );
        } else if (typeof this.metadata.activationHooks === 'string') {
          this.activationHooks = [this.metadata.activationHooks];
        } else {
          this.activationHooks = [];
        }
      } else {
        this.activationHooks = [];
      }
  
      return this.activationHooks;
    }
  
    subscribeToWorkspaceOpeners() {
      this.workspaceOpenerSubscriptions = new CompositeDisposable();
      for (let opener of this.getWorkspaceOpeners()) {
        this.workspaceOpenerSubscriptions.add(
          atom.workspace.addOpener(filePath => {
            if (filePath === opener) {
              this.activateNow();
              this.workspaceOpenerSubscriptions.dispose();
              return atom.workspace.createItemForURI(opener);
            }
          })
        );
      }
    }
  
    getWorkspaceOpeners() {
      if (this.workspaceOpeners) return this.workspaceOpeners;
  
      if (this.metadata.workspaceOpeners) {
        if (Array.isArray(this.metadata.workspaceOpeners)) {
          this.workspaceOpeners = Array.from(
            new Set(this.metadata.workspaceOpeners)
          );
        } else if (typeof this.metadata.workspaceOpeners === 'string') {
          this.workspaceOpeners = [this.metadata.workspaceOpeners];
        } else {
          this.workspaceOpeners = [];
        }
      } else {
        this.workspaceOpeners = [];
      }
  
      return this.workspaceOpeners;
    }
  
    getURIHandler() {
      return this.metadata && this.metadata.uriHandler;
    }
  
    // Does the given module path contain native code?
    isNativeModule(modulePath) {
      try {
        return this.getModulePathNodeFiles(modulePath).length > 0;
      } catch (error) {
        return false;
      }
    }
  
    // get the list of `.node` files for the given module path
    getModulePathNodeFiles(modulePath) {
      try {
        const modulePathNodeFiles = fs.listSync(
          path.join(modulePath, 'build', 'Release'),
          ['.node']
        );
        return modulePathNodeFiles;
      } catch (error) {
        return [];
      }
    }
  
    // Get a Map of all the native modules => the `.node` files that this package depends on.
    //
    // First try to get this information from
    // @metadata._atomModuleCache.extensions. If @metadata._atomModuleCache doesn't
    // exist, recurse through all dependencies.
    getNativeModuleDependencyPathsMap() {
      const nativeModulePaths = new Map();
  
      if (this.metadata._atomModuleCache) {
        const nodeFilePaths = [];
        const relativeNativeModuleBindingPaths =
          (this.metadata._atomModuleCache.extensions &&
            this.metadata._atomModuleCache.extensions['.node']) ||
          [];
        for (let relativeNativeModuleBindingPath of relativeNativeModuleBindingPaths) {
          const nodeFilePath = path.join(
            this.path,
            relativeNativeModuleBindingPath,
            '..',
            '..',
            '..'
          );
          nodeFilePaths.push(nodeFilePath);
        }
        nativeModulePaths.set(this.path, nodeFilePaths);
        return nativeModulePaths;
      }
  
      const traversePath = nodeModulesPath => {
        try {
          for (let modulePath of fs.listSync(nodeModulesPath)) {
            const modulePathNodeFiles = this.getModulePathNodeFiles(modulePath);
            if (modulePathNodeFiles) {
              nativeModulePaths.set(modulePath, modulePathNodeFiles);
            }
            traversePath(path.join(modulePath, 'node_modules'));
          }
        } catch (error) {}
      };
  
      traversePath(path.join(this.path, 'node_modules'));
  
      return nativeModulePaths;
    }
  
    // Get an array of all the native modules that this package depends on.
    // See `getNativeModuleDependencyPathsMap` for more information
    getNativeModuleDependencyPaths() {
      return [...this.getNativeModuleDependencyPathsMap().keys()];
    }
  
    /*
    Section: Native Module Compatibility
    */
  
    // Extended: Are all native modules depended on by this package correctly
    // compiled against the current version of Atom?
    //
    // Incompatible packages cannot be activated.
    //
    // Returns a {Boolean}, true if compatible, false if incompatible.
    isCompatible() {
      if (this.compatible == null) {
        if (this.preloadedPackage) {
          this.compatible = true;
        } else if (this.getMainModulePath()) {
          this.incompatibleModules = this.getIncompatibleNativeModules();
          this.compatible =
            this.incompatibleModules.length === 0 &&
            this.getBuildFailureOutput() == null;
        } else {
          this.compatible = true;
        }
      }
      return this.compatible;
    }
  
    // Extended: Rebuild native modules in this package's dependencies for the
    // current version of Atom.
    //
    // Returns a {Promise} that resolves with an object containing `code`,
    // `stdout`, and `stderr` properties based on the results of running
    // `apm rebuild` on the package.
    rebuild() {
      return new Promise(resolve =>
        this.runRebuildProcess(result => {
          if (result.code === 0) {
            global.localStorage.removeItem(
              this.getBuildFailureOutputStorageKey()
            );
          } else {
            this.compatible = false;
            global.localStorage.setItem(
              this.getBuildFailureOutputStorageKey(),
              result.stderr
            );
          }
          global.localStorage.setItem(
            this.getIncompatibleNativeModulesStorageKey(),
            '[]'
          );
          resolve(result);
        })
      );
    }
  
    // Extended: If a previous rebuild failed, get the contents of stderr.
    //
    // Returns a {String} or null if no previous build failure occurred.
    getBuildFailureOutput() {
      return global.localStorage.getItem(this.getBuildFailureOutputStorageKey());
    }
  
    runRebuildProcess(done) {
      let stderr = '';
      let stdout = '';
      return new BufferedProcess({
        command: this.packageManager.getApmPath(),
        args: ['rebuild', '--no-color'],
        options: { cwd: this.path },
        stderr(output) {
          stderr += output;
        },
        stdout(output) {
          stdout += output;
        },
        exit(code) {
          done({ code, stdout, stderr });
        }
      });
    }
  
    getBuildFailureOutputStorageKey() {
      return `installed-packages:${this.name}:${
        this.metadata.version
      }:build-error`;
    }
  
    getIncompatibleNativeModulesStorageKey() {
      const electronVersion = process.versions.electron;
      return `installed-packages:${this.name}:${
        this.metadata.version
      }:electron-${electronVersion}:incompatible-native-modules`;
    }
  
    getCanDeferMainModuleRequireStorageKey() {
      return `installed-packages:${this.name}:${
        this.metadata.version
      }:can-defer-main-module-require`;
    }
  
    // Get the incompatible native modules that this package depends on.
    // This recurses through all dependencies and requires all `.node` files.
    //
    // This information is cached in local storage on a per package/version basis
    // to minimize the impact on startup time.
    getIncompatibleNativeModules() {
      if (!this.packageManager.devMode) {
        try {
          const arrayAsString = global.localStorage.getItem(
            this.getIncompatibleNativeModulesStorageKey()
          );
          if (arrayAsString) return JSON.parse(arrayAsString);
        } catch (error1) {}
      }
  
      const incompatibleNativeModules = [];
      const nativeModulePaths = this.getNativeModuleDependencyPathsMap();
      for (const [nativeModulePath, nodeFilesPaths] of nativeModulePaths) {
        try {
          // require each .node file
          for (const nodeFilePath of nodeFilesPaths) {
            require(nodeFilePath);
          }
        } catch (error) {
          let version;
          try {
            ({ version } = require(`${nativeModulePath}/package.json`));
          } catch (error2) {}
          incompatibleNativeModules.push({
            path: nativeModulePath,
            name: path.basename(nativeModulePath),
            version,
            error: error.message
          });
        }
      }
  
      global.localStorage.setItem(
        this.getIncompatibleNativeModulesStorageKey(),
        JSON.stringify(incompatibleNativeModules)
      );
  
      return incompatibleNativeModules;
    }
  
    handleError(message, error) {
      if (atom.inSpecMode()) throw error;
  
      let detail, location, stack;
      if (error.filename && error.location && error instanceof SyntaxError) {
        location = `${error.filename}:${error.location.first_line + 1}:${error
          .location.first_column + 1}`;
        detail = `${error.message} in ${location}`;
        stack = 'SyntaxError: ' + error.message + '\n' + 'at ' + location;
      } else if (
        error.less &&
        error.filename &&
        error.column != null &&
        error.line != null
      ) {
        location = `${error.filename}:${error.line}:${error.column}`;
        detail = `${error.message} in ${location}`;
        stack = 'LessError: ' + error.message + '\n' + 'at ' + location;
      } else {
        detail = error.message;
        stack = error.stack || error;
      }
  
      this.notificationManager.addFatalError(message, {
        stack,
        detail,
        packageName: this.name,
        dismissable: true
      });
    }
  };
  
  class SettingsFile {
    static load(path, callback) {
      CSON.readFile(path, (error, properties = {}) => {
        if (error) {
          callback(error);
        } else {
          callback(null, new SettingsFile(path, properties));
        }
      });
    }
  
    constructor(path, properties) {
      this.path = path;
      this.properties = properties;
    }
  
    activate(config) {
      for (let selector in this.properties) {
        config.set(null, this.properties[selector], {
          scopeSelector: selector,
          source: this.path
        });
      }
    }
  
    deactivate(config) {
      for (let selector in this.properties) {
        config.unset(null, { scopeSelector: selector, source: this.path });
      }
    }
  }
  