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 };