/ src / package-manager.js
package-manager.js
   1  const path = require('path');
   2  let normalizePackageData = null;
   3  
   4  const _ = require('underscore-plus');
   5  const { Emitter } = require('event-kit');
   6  const fs = require('fs-plus');
   7  const CSON = require('season');
   8  
   9  const ServiceHub = require('service-hub');
  10  const Package = require('./package');
  11  const ThemePackage = require('./theme-package');
  12  const ModuleCache = require('./module-cache');
  13  const packageJSON = require('../package.json');
  14  
  15  // Extended: Package manager for coordinating the lifecycle of Atom packages.
  16  //
  17  // An instance of this class is always available as the `atom.packages` global.
  18  //
  19  // Packages can be loaded, activated, and deactivated, and unloaded:
  20  //  * Loading a package reads and parses the package's metadata and resources
  21  //    such as keymaps, menus, stylesheets, etc.
  22  //  * Activating a package registers the loaded resources and calls `activate()`
  23  //    on the package's main module.
  24  //  * Deactivating a package unregisters the package's resources  and calls
  25  //    `deactivate()` on the package's main module.
  26  //  * Unloading a package removes it completely from the package manager.
  27  //
  28  // Packages can be enabled/disabled via the `core.disabledPackages` config
  29  // settings and also by calling `enablePackage()/disablePackage()`.
  30  module.exports = class PackageManager {
  31    constructor(params) {
  32      ({
  33        config: this.config,
  34        styleManager: this.styleManager,
  35        notificationManager: this.notificationManager,
  36        keymapManager: this.keymapManager,
  37        commandRegistry: this.commandRegistry,
  38        grammarRegistry: this.grammarRegistry,
  39        deserializerManager: this.deserializerManager,
  40        viewRegistry: this.viewRegistry,
  41        uriHandlerRegistry: this.uriHandlerRegistry
  42      } = params);
  43  
  44      this.emitter = new Emitter();
  45      this.activationHookEmitter = new Emitter();
  46      this.packageDirPaths = [];
  47      this.deferredActivationHooks = [];
  48      this.triggeredActivationHooks = new Set();
  49      this.packagesCache =
  50        packageJSON._atomPackages != null ? packageJSON._atomPackages : {};
  51      this.packageDependencies =
  52        packageJSON.packageDependencies != null
  53          ? packageJSON.packageDependencies
  54          : {};
  55      this.deprecatedPackages = packageJSON._deprecatedPackages || {};
  56      this.deprecatedPackageRanges = {};
  57      this.initialPackagesLoaded = false;
  58      this.initialPackagesActivated = false;
  59      this.preloadedPackages = {};
  60      this.loadedPackages = {};
  61      this.activePackages = {};
  62      this.activatingPackages = {};
  63      this.packageStates = {};
  64      this.serviceHub = new ServiceHub();
  65  
  66      this.packageActivators = [];
  67      this.registerPackageActivator(this, ['atom', 'textmate']);
  68    }
  69  
  70    initialize(params) {
  71      this.devMode = params.devMode;
  72      this.resourcePath = params.resourcePath;
  73      if (params.configDirPath != null && !params.safeMode) {
  74        if (this.devMode) {
  75          this.packageDirPaths.push(
  76            path.join(params.configDirPath, 'dev', 'packages')
  77          );
  78          this.packageDirPaths.push(path.join(this.resourcePath, 'packages'));
  79        }
  80        this.packageDirPaths.push(path.join(params.configDirPath, 'packages'));
  81      }
  82    }
  83  
  84    setContextMenuManager(contextMenuManager) {
  85      this.contextMenuManager = contextMenuManager;
  86    }
  87  
  88    setMenuManager(menuManager) {
  89      this.menuManager = menuManager;
  90    }
  91  
  92    setThemeManager(themeManager) {
  93      this.themeManager = themeManager;
  94    }
  95  
  96    async reset() {
  97      this.serviceHub.clear();
  98      await this.deactivatePackages();
  99      this.loadedPackages = {};
 100      this.preloadedPackages = {};
 101      this.packageStates = {};
 102      this.packagesCache =
 103        packageJSON._atomPackages != null ? packageJSON._atomPackages : {};
 104      this.packageDependencies =
 105        packageJSON.packageDependencies != null
 106          ? packageJSON.packageDependencies
 107          : {};
 108      this.triggeredActivationHooks.clear();
 109      this.activatePromise = null;
 110    }
 111  
 112    /*
 113    Section: Event Subscription
 114    */
 115  
 116    // Public: Invoke the given callback when all packages have been loaded.
 117    //
 118    // * `callback` {Function}
 119    //
 120    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 121    onDidLoadInitialPackages(callback) {
 122      return this.emitter.on('did-load-initial-packages', callback);
 123    }
 124  
 125    // Public: Invoke the given callback when all packages have been activated.
 126    //
 127    // * `callback` {Function}
 128    //
 129    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 130    onDidActivateInitialPackages(callback) {
 131      return this.emitter.on('did-activate-initial-packages', callback);
 132    }
 133  
 134    getActivatePromise() {
 135      if (this.activatePromise) {
 136        return this.activatePromise;
 137      } else {
 138        return Promise.resolve();
 139      }
 140    }
 141  
 142    // Public: Invoke the given callback when a package is activated.
 143    //
 144    // * `callback` A {Function} to be invoked when a package is activated.
 145    //   * `package` The {Package} that was activated.
 146    //
 147    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 148    onDidActivatePackage(callback) {
 149      return this.emitter.on('did-activate-package', callback);
 150    }
 151  
 152    // Public: Invoke the given callback when a package is deactivated.
 153    //
 154    // * `callback` A {Function} to be invoked when a package is deactivated.
 155    //   * `package` The {Package} that was deactivated.
 156    //
 157    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 158    onDidDeactivatePackage(callback) {
 159      return this.emitter.on('did-deactivate-package', callback);
 160    }
 161  
 162    // Public: Invoke the given callback when a package is loaded.
 163    //
 164    // * `callback` A {Function} to be invoked when a package is loaded.
 165    //   * `package` The {Package} that was loaded.
 166    //
 167    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 168    onDidLoadPackage(callback) {
 169      return this.emitter.on('did-load-package', callback);
 170    }
 171  
 172    // Public: Invoke the given callback when a package is unloaded.
 173    //
 174    // * `callback` A {Function} to be invoked when a package is unloaded.
 175    //   * `package` The {Package} that was unloaded.
 176    //
 177    // Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
 178    onDidUnloadPackage(callback) {
 179      return this.emitter.on('did-unload-package', callback);
 180    }
 181  
 182    /*
 183    Section: Package system data
 184    */
 185  
 186    // Public: Get the path to the apm command.
 187    //
 188    // Uses the value of the `core.apmPath` config setting if it exists.
 189    //
 190    // Return a {String} file path to apm.
 191    getApmPath() {
 192      const configPath = atom.config.get('core.apmPath');
 193      if (configPath || this.apmPath) {
 194        return configPath || this.apmPath;
 195      }
 196  
 197      const commandName = process.platform === 'win32' ? 'apm.cmd' : 'apm';
 198      const apmRoot = path.join(process.resourcesPath, 'app', 'apm');
 199      this.apmPath = path.join(apmRoot, 'bin', commandName);
 200      if (!fs.isFileSync(this.apmPath)) {
 201        this.apmPath = path.join(
 202          apmRoot,
 203          'node_modules',
 204          'atom-package-manager',
 205          'bin',
 206          commandName
 207        );
 208      }
 209      return this.apmPath;
 210    }
 211  
 212    // Public: Get the paths being used to look for packages.
 213    //
 214    // Returns an {Array} of {String} directory paths.
 215    getPackageDirPaths() {
 216      return _.clone(this.packageDirPaths);
 217    }
 218  
 219    /*
 220    Section: General package data
 221    */
 222  
 223    // Public: Resolve the given package name to a path on disk.
 224    //
 225    // * `name` - The {String} package name.
 226    //
 227    // Return a {String} folder path or undefined if it could not be resolved.
 228    resolvePackagePath(name) {
 229      if (fs.isDirectorySync(name)) {
 230        return name;
 231      }
 232  
 233      let packagePath = fs.resolve(...this.packageDirPaths, name);
 234      if (fs.isDirectorySync(packagePath)) {
 235        return packagePath;
 236      }
 237  
 238      packagePath = path.join(this.resourcePath, 'node_modules', name);
 239      if (this.hasAtomEngine(packagePath)) {
 240        return packagePath;
 241      }
 242  
 243      return null;
 244    }
 245  
 246    // Public: Is the package with the given name bundled with Atom?
 247    //
 248    // * `name` - The {String} package name.
 249    //
 250    // Returns a {Boolean}.
 251    isBundledPackage(name) {
 252      return this.getPackageDependencies().hasOwnProperty(name);
 253    }
 254  
 255    isDeprecatedPackage(name, version) {
 256      const metadata = this.deprecatedPackages[name];
 257      if (!metadata) return false;
 258      if (!metadata.version) return true;
 259  
 260      let range = this.deprecatedPackageRanges[metadata.version];
 261      if (!range) {
 262        try {
 263          range = new ModuleCache.Range(metadata.version);
 264        } catch (error) {
 265          range = NullVersionRange;
 266        }
 267        this.deprecatedPackageRanges[metadata.version] = range;
 268      }
 269      return range.test(version);
 270    }
 271  
 272    getDeprecatedPackageMetadata(name) {
 273      const metadata = this.deprecatedPackages[name];
 274      if (metadata) Object.freeze(metadata);
 275      return metadata;
 276    }
 277  
 278    /*
 279    Section: Enabling and disabling packages
 280    */
 281  
 282    // Public: Enable the package with the given name.
 283    //
 284    // * `name` - The {String} package name.
 285    //
 286    // Returns the {Package} that was enabled or null if it isn't loaded.
 287    enablePackage(name) {
 288      const pack = this.loadPackage(name);
 289      if (pack != null) {
 290        pack.enable();
 291      }
 292      return pack;
 293    }
 294  
 295    // Public: Disable the package with the given name.
 296    //
 297    // * `name` - The {String} package name.
 298    //
 299    // Returns the {Package} that was disabled or null if it isn't loaded.
 300    disablePackage(name) {
 301      const pack = this.loadPackage(name);
 302      if (!this.isPackageDisabled(name) && pack != null) {
 303        pack.disable();
 304      }
 305      return pack;
 306    }
 307  
 308    // Public: Is the package with the given name disabled?
 309    //
 310    // * `name` - The {String} package name.
 311    //
 312    // Returns a {Boolean}.
 313    isPackageDisabled(name) {
 314      return _.include(this.config.get('core.disabledPackages') || [], name);
 315    }
 316  
 317    /*
 318    Section: Accessing active packages
 319    */
 320  
 321    // Public: Get an {Array} of all the active {Package}s.
 322    getActivePackages() {
 323      return _.values(this.activePackages);
 324    }
 325  
 326    // Public: Get the active {Package} with the given name.
 327    //
 328    // * `name` - The {String} package name.
 329    //
 330    // Returns a {Package} or undefined.
 331    getActivePackage(name) {
 332      return this.activePackages[name];
 333    }
 334  
 335    // Public: Is the {Package} with the given name active?
 336    //
 337    // * `name` - The {String} package name.
 338    //
 339    // Returns a {Boolean}.
 340    isPackageActive(name) {
 341      return this.getActivePackage(name) != null;
 342    }
 343  
 344    // Public: Returns a {Boolean} indicating whether package activation has occurred.
 345    hasActivatedInitialPackages() {
 346      return this.initialPackagesActivated;
 347    }
 348  
 349    /*
 350    Section: Accessing loaded packages
 351    */
 352  
 353    // Public: Get an {Array} of all the loaded {Package}s
 354    getLoadedPackages() {
 355      return _.values(this.loadedPackages);
 356    }
 357  
 358    // Get packages for a certain package type
 359    //
 360    // * `types` an {Array} of {String}s like ['atom', 'textmate'].
 361    getLoadedPackagesForTypes(types) {
 362      return this.getLoadedPackages().filter(p => types.includes(p.getType()));
 363    }
 364  
 365    // Public: Get the loaded {Package} with the given name.
 366    //
 367    // * `name` - The {String} package name.
 368    //
 369    // Returns a {Package} or undefined.
 370    getLoadedPackage(name) {
 371      return this.loadedPackages[name];
 372    }
 373  
 374    // Public: Is the package with the given name loaded?
 375    //
 376    // * `name` - The {String} package name.
 377    //
 378    // Returns a {Boolean}.
 379    isPackageLoaded(name) {
 380      return this.getLoadedPackage(name) != null;
 381    }
 382  
 383    // Public: Returns a {Boolean} indicating whether package loading has occurred.
 384    hasLoadedInitialPackages() {
 385      return this.initialPackagesLoaded;
 386    }
 387  
 388    /*
 389    Section: Accessing available packages
 390    */
 391  
 392    // Public: Returns an {Array} of {String}s of all the available package paths.
 393    getAvailablePackagePaths() {
 394      return this.getAvailablePackages().map(a => a.path);
 395    }
 396  
 397    // Public: Returns an {Array} of {String}s of all the available package names.
 398    getAvailablePackageNames() {
 399      return this.getAvailablePackages().map(a => a.name);
 400    }
 401  
 402    // Public: Returns an {Array} of {String}s of all the available package metadata.
 403    getAvailablePackageMetadata() {
 404      const packages = [];
 405      for (const pack of this.getAvailablePackages()) {
 406        const loadedPackage = this.getLoadedPackage(pack.name);
 407        const metadata =
 408          loadedPackage != null
 409            ? loadedPackage.metadata
 410            : this.loadPackageMetadata(pack, true);
 411        packages.push(metadata);
 412      }
 413      return packages;
 414    }
 415  
 416    getAvailablePackages() {
 417      const packages = [];
 418      const packagesByName = new Set();
 419  
 420      for (const packageDirPath of this.packageDirPaths) {
 421        if (fs.isDirectorySync(packageDirPath)) {
 422          for (let packagePath of fs.readdirSync(packageDirPath)) {
 423            packagePath = path.join(packageDirPath, packagePath);
 424            const packageName = path.basename(packagePath);
 425            if (
 426              !packageName.startsWith('.') &&
 427              !packagesByName.has(packageName) &&
 428              fs.isDirectorySync(packagePath)
 429            ) {
 430              packages.push({
 431                name: packageName,
 432                path: packagePath,
 433                isBundled: false
 434              });
 435              packagesByName.add(packageName);
 436            }
 437          }
 438        }
 439      }
 440  
 441      for (const packageName in this.packageDependencies) {
 442        if (!packagesByName.has(packageName)) {
 443          packages.push({
 444            name: packageName,
 445            path: path.join(this.resourcePath, 'node_modules', packageName),
 446            isBundled: true
 447          });
 448        }
 449      }
 450  
 451      return packages.sort((a, b) => a.name.localeCompare(b.name));
 452    }
 453  
 454    /*
 455    Section: Private
 456    */
 457  
 458    getPackageState(name) {
 459      return this.packageStates[name];
 460    }
 461  
 462    setPackageState(name, state) {
 463      this.packageStates[name] = state;
 464    }
 465  
 466    getPackageDependencies() {
 467      return this.packageDependencies;
 468    }
 469  
 470    hasAtomEngine(packagePath) {
 471      const metadata = this.loadPackageMetadata(packagePath, true);
 472      return (
 473        metadata != null &&
 474        metadata.engines != null &&
 475        metadata.engines.atom != null
 476      );
 477    }
 478  
 479    unobserveDisabledPackages() {
 480      if (this.disabledPackagesSubscription != null) {
 481        this.disabledPackagesSubscription.dispose();
 482      }
 483      this.disabledPackagesSubscription = null;
 484    }
 485  
 486    observeDisabledPackages() {
 487      if (this.disabledPackagesSubscription != null) {
 488        return;
 489      }
 490  
 491      this.disabledPackagesSubscription = this.config.onDidChange(
 492        'core.disabledPackages',
 493        ({ newValue, oldValue }) => {
 494          const packagesToEnable = _.difference(oldValue, newValue);
 495          const packagesToDisable = _.difference(newValue, oldValue);
 496          packagesToDisable.forEach(name => {
 497            if (this.getActivePackage(name)) this.deactivatePackage(name);
 498          });
 499          packagesToEnable.forEach(name => this.activatePackage(name));
 500          return null;
 501        }
 502      );
 503    }
 504  
 505    unobservePackagesWithKeymapsDisabled() {
 506      if (this.packagesWithKeymapsDisabledSubscription != null) {
 507        this.packagesWithKeymapsDisabledSubscription.dispose();
 508      }
 509      this.packagesWithKeymapsDisabledSubscription = null;
 510    }
 511  
 512    observePackagesWithKeymapsDisabled() {
 513      if (this.packagesWithKeymapsDisabledSubscription != null) {
 514        return;
 515      }
 516  
 517      const performOnLoadedActivePackages = (
 518        packageNames,
 519        disabledPackageNames,
 520        action
 521      ) => {
 522        for (const packageName of packageNames) {
 523          if (!disabledPackageNames.has(packageName)) {
 524            var pack = this.getLoadedPackage(packageName);
 525            if (pack != null) {
 526              action(pack);
 527            }
 528          }
 529        }
 530      };
 531  
 532      this.packagesWithKeymapsDisabledSubscription = this.config.onDidChange(
 533        'core.packagesWithKeymapsDisabled',
 534        ({ newValue, oldValue }) => {
 535          const keymapsToEnable = _.difference(oldValue, newValue);
 536          const keymapsToDisable = _.difference(newValue, oldValue);
 537  
 538          const disabledPackageNames = new Set(
 539            this.config.get('core.disabledPackages')
 540          );
 541          performOnLoadedActivePackages(
 542            keymapsToDisable,
 543            disabledPackageNames,
 544            p => p.deactivateKeymaps()
 545          );
 546          performOnLoadedActivePackages(
 547            keymapsToEnable,
 548            disabledPackageNames,
 549            p => p.activateKeymaps()
 550          );
 551          return null;
 552        }
 553      );
 554    }
 555  
 556    preloadPackages() {
 557      const result = [];
 558      for (const packageName in this.packagesCache) {
 559        result.push(
 560          this.preloadPackage(packageName, this.packagesCache[packageName])
 561        );
 562      }
 563      return result;
 564    }
 565  
 566    preloadPackage(packageName, pack) {
 567      const metadata = pack.metadata || {};
 568      if (typeof metadata.name !== 'string' || metadata.name.length < 1) {
 569        metadata.name = packageName;
 570      }
 571  
 572      if (
 573        metadata.repository != null &&
 574        metadata.repository.type === 'git' &&
 575        typeof metadata.repository.url === 'string'
 576      ) {
 577        metadata.repository.url = metadata.repository.url.replace(
 578          /(^git\+)|(\.git$)/g,
 579          ''
 580        );
 581      }
 582  
 583      const options = {
 584        path: pack.rootDirPath,
 585        name: packageName,
 586        preloadedPackage: true,
 587        bundledPackage: true,
 588        metadata,
 589        packageManager: this,
 590        config: this.config,
 591        styleManager: this.styleManager,
 592        commandRegistry: this.commandRegistry,
 593        keymapManager: this.keymapManager,
 594        notificationManager: this.notificationManager,
 595        grammarRegistry: this.grammarRegistry,
 596        themeManager: this.themeManager,
 597        menuManager: this.menuManager,
 598        contextMenuManager: this.contextMenuManager,
 599        deserializerManager: this.deserializerManager,
 600        viewRegistry: this.viewRegistry
 601      };
 602  
 603      pack = metadata.theme ? new ThemePackage(options) : new Package(options);
 604      pack.preload();
 605      this.preloadedPackages[packageName] = pack;
 606      return pack;
 607    }
 608  
 609    loadPackages() {
 610      // Ensure atom exports is already in the require cache so the load time
 611      // of the first package isn't skewed by being the first to require atom
 612      require('../exports/atom');
 613  
 614      const disabledPackageNames = new Set(
 615        this.config.get('core.disabledPackages')
 616      );
 617      this.config.transact(() => {
 618        for (const pack of this.getAvailablePackages()) {
 619          this.loadAvailablePackage(pack, disabledPackageNames);
 620        }
 621      });
 622      this.initialPackagesLoaded = true;
 623      this.emitter.emit('did-load-initial-packages');
 624    }
 625  
 626    loadPackage(nameOrPath) {
 627      if (path.basename(nameOrPath)[0].match(/^\./)) {
 628        // primarily to skip .git folder
 629        return null;
 630      }
 631  
 632      const pack = this.getLoadedPackage(nameOrPath);
 633      if (pack) {
 634        return pack;
 635      }
 636  
 637      const packagePath = this.resolvePackagePath(nameOrPath);
 638      if (packagePath) {
 639        const name = path.basename(nameOrPath);
 640        return this.loadAvailablePackage({
 641          name,
 642          path: packagePath,
 643          isBundled: this.isBundledPackagePath(packagePath)
 644        });
 645      }
 646  
 647      console.warn(`Could not resolve '${nameOrPath}' to a package path`);
 648      return null;
 649    }
 650  
 651    loadAvailablePackage(availablePackage, disabledPackageNames) {
 652      const preloadedPackage = this.preloadedPackages[availablePackage.name];
 653  
 654      if (
 655        disabledPackageNames != null &&
 656        disabledPackageNames.has(availablePackage.name)
 657      ) {
 658        if (preloadedPackage != null) {
 659          preloadedPackage.deactivate();
 660          delete preloadedPackage[availablePackage.name];
 661        }
 662        return null;
 663      }
 664  
 665      const loadedPackage = this.getLoadedPackage(availablePackage.name);
 666      if (loadedPackage != null) {
 667        return loadedPackage;
 668      }
 669  
 670      if (preloadedPackage != null) {
 671        if (availablePackage.isBundled) {
 672          preloadedPackage.finishLoading();
 673          this.loadedPackages[availablePackage.name] = preloadedPackage;
 674          return preloadedPackage;
 675        } else {
 676          preloadedPackage.deactivate();
 677          delete preloadedPackage[availablePackage.name];
 678        }
 679      }
 680  
 681      let metadata;
 682      try {
 683        metadata = this.loadPackageMetadata(availablePackage) || {};
 684      } catch (error) {
 685        this.handleMetadataError(error, availablePackage.path);
 686        return null;
 687      }
 688  
 689      if (
 690        !availablePackage.isBundled &&
 691        this.isDeprecatedPackage(metadata.name, metadata.version)
 692      ) {
 693        console.warn(
 694          `Could not load ${metadata.name}@${
 695            metadata.version
 696          } because it uses deprecated APIs that have been removed.`
 697        );
 698        return null;
 699      }
 700  
 701      const options = {
 702        path: availablePackage.path,
 703        name: availablePackage.name,
 704        metadata,
 705        bundledPackage: availablePackage.isBundled,
 706        packageManager: this,
 707        config: this.config,
 708        styleManager: this.styleManager,
 709        commandRegistry: this.commandRegistry,
 710        keymapManager: this.keymapManager,
 711        notificationManager: this.notificationManager,
 712        grammarRegistry: this.grammarRegistry,
 713        themeManager: this.themeManager,
 714        menuManager: this.menuManager,
 715        contextMenuManager: this.contextMenuManager,
 716        deserializerManager: this.deserializerManager,
 717        viewRegistry: this.viewRegistry
 718      };
 719  
 720      const pack = metadata.theme
 721        ? new ThemePackage(options)
 722        : new Package(options);
 723      pack.load();
 724      this.loadedPackages[pack.name] = pack;
 725      this.emitter.emit('did-load-package', pack);
 726      return pack;
 727    }
 728  
 729    unloadPackages() {
 730      _.keys(this.loadedPackages).forEach(name => this.unloadPackage(name));
 731    }
 732  
 733    unloadPackage(name) {
 734      if (this.isPackageActive(name)) {
 735        throw new Error(`Tried to unload active package '${name}'`);
 736      }
 737  
 738      const pack = this.getLoadedPackage(name);
 739      if (pack) {
 740        delete this.loadedPackages[pack.name];
 741        this.emitter.emit('did-unload-package', pack);
 742      } else {
 743        throw new Error(`No loaded package for name '${name}'`);
 744      }
 745    }
 746  
 747    // Activate all the packages that should be activated.
 748    activate() {
 749      let promises = [];
 750      for (let [activator, types] of this.packageActivators) {
 751        const packages = this.getLoadedPackagesForTypes(types);
 752        promises = promises.concat(activator.activatePackages(packages));
 753      }
 754      this.activatePromise = Promise.all(promises).then(() => {
 755        this.triggerDeferredActivationHooks();
 756        this.initialPackagesActivated = true;
 757        this.emitter.emit('did-activate-initial-packages');
 758        this.activatePromise = null;
 759      });
 760      return this.activatePromise;
 761    }
 762  
 763    registerURIHandlerForPackage(packageName, handler) {
 764      return this.uriHandlerRegistry.registerHostHandler(packageName, handler);
 765    }
 766  
 767    // another type of package manager can handle other package types.
 768    // See ThemeManager
 769    registerPackageActivator(activator, types) {
 770      this.packageActivators.push([activator, types]);
 771    }
 772  
 773    activatePackages(packages) {
 774      const promises = [];
 775      this.config.transactAsync(() => {
 776        for (const pack of packages) {
 777          const promise = this.activatePackage(pack.name);
 778          if (!pack.activationShouldBeDeferred()) {
 779            promises.push(promise);
 780          }
 781        }
 782        return Promise.all(promises);
 783      });
 784      this.observeDisabledPackages();
 785      this.observePackagesWithKeymapsDisabled();
 786      return promises;
 787    }
 788  
 789    // Activate a single package by name
 790    activatePackage(name) {
 791      let pack = this.getActivePackage(name);
 792      if (pack) {
 793        return Promise.resolve(pack);
 794      }
 795  
 796      pack = this.loadPackage(name);
 797      if (!pack) {
 798        return Promise.reject(new Error(`Failed to load package '${name}'`));
 799      }
 800  
 801      this.activatingPackages[pack.name] = pack;
 802      const activationPromise = pack.activate().then(() => {
 803        if (this.activatingPackages[pack.name] != null) {
 804          delete this.activatingPackages[pack.name];
 805          this.activePackages[pack.name] = pack;
 806          this.emitter.emit('did-activate-package', pack);
 807        }
 808        return pack;
 809      });
 810  
 811      if (this.deferredActivationHooks == null) {
 812        this.triggeredActivationHooks.forEach(hook =>
 813          this.activationHookEmitter.emit(hook)
 814        );
 815      }
 816  
 817      return activationPromise;
 818    }
 819  
 820    triggerDeferredActivationHooks() {
 821      if (this.deferredActivationHooks == null) {
 822        return;
 823      }
 824  
 825      for (const hook of this.deferredActivationHooks) {
 826        this.activationHookEmitter.emit(hook);
 827      }
 828  
 829      this.deferredActivationHooks = null;
 830    }
 831  
 832    triggerActivationHook(hook) {
 833      if (hook == null || !_.isString(hook) || hook.length <= 0) {
 834        return new Error('Cannot trigger an empty activation hook');
 835      }
 836  
 837      this.triggeredActivationHooks.add(hook);
 838      if (this.deferredActivationHooks != null) {
 839        this.deferredActivationHooks.push(hook);
 840      } else {
 841        this.activationHookEmitter.emit(hook);
 842      }
 843    }
 844  
 845    onDidTriggerActivationHook(hook, callback) {
 846      if (hook == null || !_.isString(hook) || hook.length <= 0) {
 847        return;
 848      }
 849      return this.activationHookEmitter.on(hook, callback);
 850    }
 851  
 852    serialize() {
 853      for (const pack of this.getActivePackages()) {
 854        this.serializePackage(pack);
 855      }
 856      return this.packageStates;
 857    }
 858  
 859    serializePackage(pack) {
 860      if (typeof pack.serialize === 'function') {
 861        this.setPackageState(pack.name, pack.serialize());
 862      }
 863    }
 864  
 865    // Deactivate all packages
 866    async deactivatePackages() {
 867      await this.config.transactAsync(() =>
 868        Promise.all(
 869          this.getLoadedPackages().map(pack =>
 870            this.deactivatePackage(pack.name, true)
 871          )
 872        )
 873      );
 874      this.unobserveDisabledPackages();
 875      this.unobservePackagesWithKeymapsDisabled();
 876    }
 877  
 878    // Deactivate the package with the given name
 879    async deactivatePackage(name, suppressSerialization) {
 880      const pack = this.getLoadedPackage(name);
 881      if (pack == null) {
 882        return;
 883      }
 884  
 885      if (!suppressSerialization && this.isPackageActive(pack.name)) {
 886        this.serializePackage(pack);
 887      }
 888  
 889      const deactivationResult = pack.deactivate();
 890      if (deactivationResult && typeof deactivationResult.then === 'function') {
 891        await deactivationResult;
 892      }
 893  
 894      delete this.activePackages[pack.name];
 895      delete this.activatingPackages[pack.name];
 896      this.emitter.emit('did-deactivate-package', pack);
 897    }
 898  
 899    handleMetadataError(error, packagePath) {
 900      const metadataPath = path.join(packagePath, 'package.json');
 901      const detail = `${error.message} in ${metadataPath}`;
 902      const stack = `${error.stack}\n  at ${metadataPath}:1:1`;
 903      const message = `Failed to load the ${path.basename(packagePath)} package`;
 904      this.notificationManager.addError(message, {
 905        stack,
 906        detail,
 907        packageName: path.basename(packagePath),
 908        dismissable: true
 909      });
 910    }
 911  
 912    uninstallDirectory(directory) {
 913      const symlinkPromise = new Promise(resolve =>
 914        fs.isSymbolicLink(directory, isSymLink => resolve(isSymLink))
 915      );
 916      const dirPromise = new Promise(resolve =>
 917        fs.isDirectory(directory, isDir => resolve(isDir))
 918      );
 919  
 920      return Promise.all([symlinkPromise, dirPromise]).then(values => {
 921        const [isSymLink, isDir] = values;
 922        if (!isSymLink && isDir) {
 923          return fs.remove(directory, function() {});
 924        }
 925      });
 926    }
 927  
 928    reloadActivePackageStyleSheets() {
 929      for (const pack of this.getActivePackages()) {
 930        if (
 931          pack.getType() !== 'theme' &&
 932          typeof pack.reloadStylesheets === 'function'
 933        ) {
 934          pack.reloadStylesheets();
 935        }
 936      }
 937    }
 938  
 939    isBundledPackagePath(packagePath) {
 940      if (
 941        this.devMode &&
 942        !this.resourcePath.startsWith(`${process.resourcesPath}${path.sep}`)
 943      ) {
 944        return false;
 945      }
 946  
 947      if (this.resourcePathWithTrailingSlash == null) {
 948        this.resourcePathWithTrailingSlash = `${this.resourcePath}${path.sep}`;
 949      }
 950  
 951      return (
 952        packagePath != null &&
 953        packagePath.startsWith(this.resourcePathWithTrailingSlash)
 954      );
 955    }
 956  
 957    loadPackageMetadata(packagePathOrAvailablePackage, ignoreErrors = false) {
 958      let isBundled, packageName, packagePath;
 959      if (typeof packagePathOrAvailablePackage === 'object') {
 960        const availablePackage = packagePathOrAvailablePackage;
 961        packageName = availablePackage.name;
 962        packagePath = availablePackage.path;
 963        isBundled = availablePackage.isBundled;
 964      } else {
 965        packagePath = packagePathOrAvailablePackage;
 966        packageName = path.basename(packagePath);
 967        isBundled = this.isBundledPackagePath(packagePath);
 968      }
 969  
 970      let metadata;
 971      if (isBundled && this.packagesCache[packageName] != null) {
 972        metadata = this.packagesCache[packageName].metadata;
 973      }
 974  
 975      if (metadata == null) {
 976        const metadataPath = CSON.resolve(path.join(packagePath, 'package'));
 977        if (metadataPath) {
 978          try {
 979            metadata = CSON.readFileSync(metadataPath);
 980            this.normalizePackageMetadata(metadata);
 981          } catch (error) {
 982            if (!ignoreErrors) {
 983              throw error;
 984            }
 985          }
 986        }
 987      }
 988  
 989      if (metadata == null) {
 990        metadata = {};
 991      }
 992  
 993      if (typeof metadata.name !== 'string' || metadata.name.length <= 0) {
 994        metadata.name = packageName;
 995      }
 996  
 997      if (
 998        metadata.repository &&
 999        metadata.repository.type === 'git' &&
1000        typeof metadata.repository.url === 'string'
1001      ) {
1002        metadata.repository.url = metadata.repository.url.replace(
1003          /(^git\+)|(\.git$)/g,
1004          ''
1005        );
1006      }
1007  
1008      return metadata;
1009    }
1010  
1011    normalizePackageMetadata(metadata) {
1012      if (metadata != null) {
1013        normalizePackageData =
1014          normalizePackageData || require('normalize-package-data');
1015        normalizePackageData(metadata);
1016      }
1017    }
1018  };
1019  
1020  const NullVersionRange = {
1021    test() {
1022      return false;
1023    }
1024  };