Modules (181)

ExtensionManager

Description

The ExtensionManager fetches/caches the extension registry and provides information about the status of installed extensions. ExtensionManager raises the following events:

  • statusChange - indicates that an extension has been installed/uninstalled or its status has otherwise changed. Second parameter is the id of the extension.
  • registryUpdate - indicates that an existing extension was synchronized with new data from the registry.

Dependencies

Variables

Public API

ENABLED

Extension status constants.

    var ENABLED      = "enabled",
        DISABLED     = "disabled",
        START_FAILED = "startFailed";

FOLDER_AUTOINSTALL

Extension auto-install folder. Also used for preferences key.

    var FOLDER_AUTOINSTALL = "auto-install-extensions";
Public API

LOCATION_DEFAULT

Extension location constants.

    var LOCATION_DEFAULT = "default",
        LOCATION_DEV     = "dev",
        LOCATION_USER    = "user",
        LOCATION_UNKNOWN = "unknown";
Private

_idsToRemove

Requested changes to the installed extensions.

    var _idsToRemove = {},
        _idsToUpdate = {},
        _idsToDisable = {};

    PreferencesManager.stateManager.definePreference(FOLDER_AUTOINSTALL, "object", undefined);
    PreferencesManager.definePreference("extensions.sort", "string", "publishedDate", {
        description: Strings.SORT_EXTENSION_METHOD
    });
Private Public API

extensions

Type
Object.<string, {metadata: Object, path: string, status: string}>
    var extensions = {};
Private

pendingDownloadRegistry

Type
$.Deferred
    var pendingDownloadRegistry = null;

Functions

Private

_autoInstallExtensions

Returns: $.Promise
Promise that resolves when finished
    function _autoInstallExtensions() {
        var dirPath        = FileUtils.getDirectoryPath(FileUtils.getNativeBracketsDirectoryPath()) + FOLDER_AUTOINSTALL + "/",
            autoExtensions = PreferencesManager.getViewState(FOLDER_AUTOINSTALL) || {},
            deferred       = new $.Deferred();

        _getAutoInstallFiles(dirPath, autoExtensions).done(function (result) {
            var installPromise = Async.doSequentially(result.installZips, function (zip) {
                autoExtensions[zip.info.metadata.name] = zip.info.metadata.version;
                return Package.installFromPath(zip.file.fullPath);
            });

            var updatePromise = installPromise.always(function () {
                return Async.doSequentially(result.updateZips, function (zip) {
                    autoExtensions[zip.info.metadata.name] = zip.info.metadata.version;
                    return Package.installUpdate(zip.file.fullPath);
                });
            });

            // Always resolve the outer promise
            updatePromise.always(function () {
                // Keep track of auto-installed extensions so we only install an extension once
                PreferencesManager.setViewState(FOLDER_AUTOINSTALL, autoExtensions);

                deferred.resolve();
            });
        });

        return deferred.promise();
    }

    AppInit.appReady(function () {
        Package._getNodeConnectionDeferred().done(function () {
            _autoInstallExtensions();
        });
    });

    // Listen to extension load and loadFailed events
    ExtensionLoader
        .on("load", _handleExtensionLoad)
        .on("loadFailed", _handleExtensionLoad)
        .on("disabled", _handleExtensionLoad);


    EventDispatcher.makeEventDispatcher(exports);

    // Public exports
    exports.downloadRegistry        = downloadRegistry;
    exports.getCompatibilityInfo    = getCompatibilityInfo;
    exports.getExtensionURL         = getExtensionURL;
    exports.remove                  = remove;
    exports.update                  = update;
    exports.disable                 = disable;
    exports.enable                  = enable;
    exports.extensions              = extensions;
    exports.cleanupUpdates          = cleanupUpdates;
    exports.markForRemoval          = markForRemoval;
    exports.isMarkedForRemoval      = isMarkedForRemoval;
    exports.unmarkAllForRemoval     = unmarkAllForRemoval;
    exports.hasExtensionsToRemove   = hasExtensionsToRemove;
    exports.markForDisabling        = markForDisabling;
    exports.isMarkedForDisabling    = isMarkedForDisabling;
    exports.unmarkAllForDisabling   = unmarkAllForDisabling;
    exports.hasExtensionsToDisable  = hasExtensionsToDisable;
    exports.updateFromDownload      = updateFromDownload;
    exports.removeUpdate            = removeUpdate;
    exports.isMarkedForUpdate       = isMarkedForUpdate;
    exports.hasExtensionsToUpdate   = hasExtensionsToUpdate;
    exports.removeMarkedExtensions  = removeMarkedExtensions;
    exports.disableMarkedExtensions = disableMarkedExtensions;
    exports.updateExtensions        = updateExtensions;
    exports.getAvailableUpdates     = getAvailableUpdates;
    exports.cleanAvailableUpdates   = cleanAvailableUpdates;

    exports.hasDownloadedRegistry   = false;

    exports.ENABLED       = ENABLED;
    exports.DISABLED      = DISABLED;
    exports.START_FAILED  = START_FAILED;

    exports.LOCATION_DEFAULT  = LOCATION_DEFAULT;
    exports.LOCATION_DEV      = LOCATION_DEV;
    exports.LOCATION_USER     = LOCATION_USER;
    exports.LOCATION_UNKNOWN  = LOCATION_UNKNOWN;

    // For unit testing only
    exports._getAutoInstallFiles    = _getAutoInstallFiles;
    exports._reset                  = _reset;
    exports._setExtensions          = _setExtensions;
});
Private

_enableOrDisable

id string
The id of the extension to disable or enable.
enable boolean
A boolean indicating whether to enable or disable.
Returns: $.Promise
A promise that's resolved when the extension action is completed or rejected with an error that prevents the action from completion.
    function _enableOrDisable(id, enable) {
        var result = new $.Deferred(),
            extension = extensions[id];
        if (extension && extension.installInfo) {
            Package[(enable ? "enable" : "disable")](extension.installInfo.path)
                .done(function () {
                    extension.installInfo.status = enable ? ENABLED : DISABLED;
                    extension.installInfo.metadata.disabled = !enable;
                    result.resolve();
                    exports.trigger("statusChange", id);
                })
                .fail(function (err) {
                    result.reject(err);
                });
        } else {
            result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id));
        }
        return result.promise();
    }
Private Public API

_getAutoInstallFiles

dirPath string
Directory with extensions
autoExtensions Object
Object that maps names of previously auto-installed extensions {string} to installed version {string}.
Returns: $.Promise
Promise that resolves with arrays for extensions to update and install
    function _getAutoInstallFiles(dirPath, autoExtensions) {
        var zipFiles    = [],
            installZips = [],
            updateZips  = [],
            deferred    = new $.Deferred();

        FileSystem.getDirectoryForPath(dirPath).getContents(function (err, contents) {
            if (!err) {
                zipFiles = contents.filter(function (dirItem) {
                    return (dirItem.isFile && FileUtils.getFileExtension(dirItem.fullPath) === "zip");
                });
            }

            // Parse zip files and separate new installs vs. updates
            Async.doInParallel_aggregateErrors(zipFiles, function (file) {
                var zipFilePromise = new $.Deferred();

                // Call validate() so that we open the local zip file and parse the
                // package.json. We need the name to detect if this zip will be a
                // new install or an update.
                Package.validate(file.fullPath, { requirePackageJSON: true }).done(function (info) {
                    if (info.errors.length) {
                        zipFilePromise.reject(Package.formatError(info.errors));
                        return;
                    }

                    var extensionInfo, installedVersion, zipArray, existingItem,
                        extensionName   = info.metadata.name,
                        autoExtVersion  = autoExtensions[extensionName];

                    // Verify extension has not already been auto-installed/updated
                    if (autoExtVersion && semver.lte(info.metadata.version, autoExtVersion)) {
                        // Have already auto installed/updated version >= version of this extension
                        zipFilePromise.reject();
                        return;
                    }

                    // Verify extension has not already been installed/updated by some other means
                    extensionInfo = extensions[extensionName];
                    installedVersion = extensionInfo && extensionInfo.installInfo && extensionInfo.installInfo.metadata.version;
                    if (installedVersion && semver.lte(info.metadata.version, installedVersion)) {
                        // Have already manually installed/updated version >= version of this extension
                        zipFilePromise.reject();
                        return;
                    }

                    // Update appropriate zip array. There could be multiple zip files for an
                    // extension, so make sure only the latest is stored
                    zipArray = (installedVersion) ? updateZips : installZips;
                    zipArray.some(function (zip) {
                        if (zip.info.metadata.name === extensionName) {
                            existingItem = zip;
                            return true;
                        }
                        return false;
                    });
                    if (existingItem) {
                        if (semver.lt(existingItem.info.metadata.version, info.metadata.version)) {
                            existingItem.file = file;
                            existingItem.info = info;
                        }
                    } else {
                        zipArray.push({ file: file, info: info });
                    }

                    zipFilePromise.resolve();
                }).fail(function (err) {
                    zipFilePromise.reject(Package.formatError(err));
                });

                return zipFilePromise.promise();
            }).fail(function (errorArray) {
                // Async.doInParallel() fails if some are successful, so write errors
                // to console and always resolve
                errorArray.forEach(function (errorObj) {
                    // If we rejected without an error argument, it means it was no problem
                    // (e.g. same version of extension is already installed)
                    if (errorObj.error) {
                        if (errorObj.error.forEach) {
                            console.error("Errors for", errorObj.item);
                            errorObj.error.forEach(function (error) {
                                console.error(Package.formatError(error));
                            });
                        } else {
                            console.error("Error for", errorObj.item, errorObj);
                        }
                    }
                });
            }).always(function () {
                deferred.resolve({
                    installZips: installZips,
                    updateZips:  updateZips
                });
            });
        });

        return deferred.promise();
    }
Private

_handleExtensionLoad

e $.Event
The event object
path string
The local path of the loaded extension's folder.
    function _handleExtensionLoad(e, path) {
        function setData(metadata) {
            var locationType,
                id = metadata.name,
                userExtensionPath = ExtensionLoader.getUserExtensionPath();
            if (path.indexOf(userExtensionPath) === 0) {
                locationType = LOCATION_USER;
            } else {
                var segments = path.split("/"), parent;
                if (segments.length > 2) {
                    parent = segments[segments.length - 2];
                }
                if (parent === "dev") {
                    locationType = LOCATION_DEV;
                } else if (parent === "default") {
                    locationType = LOCATION_DEFAULT;
                } else {
                    locationType = LOCATION_UNKNOWN;
                }
            }
            if (!extensions[id]) {
                extensions[id] = {};
            }
            extensions[id].installInfo = {
                metadata: metadata,
                path: path,
                locationType: locationType,
                status: (e.type === "loadFailed" ? START_FAILED : (e.type === "disabled" ? DISABLED : ENABLED))
            };

            synchronizeEntry(id);
            loadTheme(id);
            exports.trigger("statusChange", id);
        }

        function deduceMetadata() {
            var match = path.match(/\/([^\/]+)$/),
                name = (match && match[1]) || path,
                metadata = { name: name, title: name };
            return metadata;
        }

        ExtensionUtils.loadMetadata(path)
            .done(function (metadata) {
                setData(metadata);
            })
            .fail(function (disabled) {
                // If there's no package.json, this is a legacy extension. It was successfully loaded,
                // but we don't have an official ID or metadata for it, so we just create an id and
                // "title" for it (which is the last segment of its pathname)
                // and record that it's enabled.
                var metadata = deduceMetadata();
                metadata.disabled = disabled;
                setData(metadata);
            });
    }
Private Public API

_reset

    function _reset() {
        exports.extensions = extensions = {};
        _idsToRemove = {};
        _idsToUpdate = {};
        _idsToDisable = {};
    }
Private Public API

_setExtensions

    function _setExtensions(newExtensions) {
        exports.extensions = extensions = newExtensions;
        Object.keys(extensions).forEach(function (id) {
            synchronizeEntry(id);
        });
    }
Public API

cleanAvailableUpdates

Takes the array returned from getAvailableUpdates() as an input and removes those entries that are no longer current - when currently installed version of an extension is equal or newer than registryVersion returned by getAvailableUpdates(). This function is designed to work without the necessity to download extension registry

updates Array.<{id: string,installVersion: string,registryVersion: string}>
previous output of getAvailableUpdates()
Returns: Array.<{id: string,installVersion: string,registryVersion: string}>
filtered input as function description
    function cleanAvailableUpdates(updates) {
        return updates.reduce(function (arr, updateInfo) {
            var extDefinition = extensions[updateInfo.id];
            if (!extDefinition || !extDefinition.installInfo) {
                // extension has been uninstalled in the meantime
                return arr;
            }

            var installedVersion = extDefinition.installInfo.metadata.version;
            if (semver.lt(installedVersion, updateInfo.registryVersion)) {
                arr.push(updateInfo);
            }

            return arr;
        }, []);
    }
Public API

cleanupUpdates

Deletes any temporary files left behind by extensions that were marked for update.

    function cleanupUpdates() {
        Object.keys(_idsToUpdate).forEach(function (id) {
            var installResult = _idsToUpdate[id],
                keepFile = installResult.keepFile,
                filename = installResult.localPath;

            if (filename && !keepFile) {
                FileSystem.getFileForPath(filename).unlink();
            }
        });
        _idsToUpdate = {};
    }
Public API

disable

Disables the installed extension with the given id.

id string
The id of the extension to disable.
Returns: $.Promise
A promise that's resolved when the extenion is disabled or rejected with an error that prevented the disabling.
    function disable(id) {
        return _enableOrDisable(id, false);
    }
Public API

disableMarkedExtensions

Disables extensions marked for disabling.

If the return promise is rejected, the argument will contain an array of objects. Each element is an object identifying the extension failed with "item" property set to the extension id which has failed to be disabled and "error" property set to the error.

Returns: $.Promise
A promise that's resolved when all extensions marked for disabling are disabled or rejected if one or more extensions can't be disabled.
    function disableMarkedExtensions() {
        return Async.doInParallel_aggregateErrors(
            Object.keys(_idsToDisable),
            function (id) {
                return disable(id);
            }
        );
    }
Public API

downloadRegistry

Downloads the registry of Brackets extensions and stores the information in our extension info.

Returns: $.Promise
a promise that's resolved with the registry JSON data or rejected if the server can't be reached.
    function downloadRegistry() {
        if (pendingDownloadRegistry) {
            return pendingDownloadRegistry.promise();
        }

        pendingDownloadRegistry = new $.Deferred();

        $.ajax({
            url: brackets.config.extension_registry,
            dataType: "json",
            cache: false
        })
            .done(function (data) {
                exports.hasDownloadedRegistry = true;
                Object.keys(data).forEach(function (id) {
                    if (!extensions[id]) {
                        extensions[id] = {};
                    }
                    extensions[id].registryInfo = data[id];
                    synchronizeEntry(id);
                });
                exports.trigger("registryDownload");
                pendingDownloadRegistry.resolve();
            })
            .fail(function () {
                pendingDownloadRegistry.reject();
            })
            .always(function () {
                // Make sure to clean up the pending registry so that new requests can be made.
                pendingDownloadRegistry = null;
            });

        return pendingDownloadRegistry.promise();
    }
Public API

enable

Enables the installed extension with the given id.

id string
The id of the extension to enable.
Returns: $.Promise
A promise that's resolved when the extenion is enabled or rejected with an error that prevented the enabling.
    function enable(id) {
        return _enableOrDisable(id, true);
    }
Public API

getAvailableUpdates

Gets an array of extensions that are currently installed and can be updated to a new version

Returns: Array.<{id: string,installVersion: string,registryVersion: string}>
where id = extensionId installVersion = currently installed version of extension registryVersion = latest version compatible with current Brackets
    function getAvailableUpdates() {
        var result = [];
        Object.keys(extensions).forEach(function (extensionId) {
            var extensionInfo = extensions[extensionId];
            // skip extensions that are not installed or are not in the registry
            if (!extensionInfo.installInfo || !extensionInfo.registryInfo) {
                return;
            }
            if (extensionInfo.registryInfo.updateCompatible) {
                result.push({
                    id: extensionId,
                    installVersion: extensionInfo.installInfo.metadata.version,
                    registryVersion: extensionInfo.registryInfo.lastCompatibleVersion
                });
            }
        });
        return result;
    }
Public API

getCompatibilityInfo

Finds the newest version of the entry that is compatible with the given Brackets API version, if any.

entry Object
The registry entry to check.
apiVersion string
The Brackets API version to check against.
Returns: {isCompatible: boolean,requiresNewer: ?boolean,compatibleVersion: ?string,isLatestVersion: boolean}
Result contains an "isCompatible" member saying whether it's compatible. If compatible, "compatibleVersion" specifies the newest version that is compatible and "isLatestVersion" indicates if this is the absolute latest version of the extension or not. If !isCompatible or !isLatestVersion, "requiresNewer" says whether the latest version is incompatible due to requiring a newer (vs. older) version of Brackets.
    function getCompatibilityInfo(entry, apiVersion) {
        if (!entry.versions) {
            var fallback = getCompatibilityInfoForVersion(entry.metadata, apiVersion);
            if (fallback.isCompatible) {
                fallback.isLatestVersion = true;
            }
            return fallback;
        }

        var i = entry.versions.length - 1,
            latestInfo = getCompatibilityInfoForVersion(entry.versions[i], apiVersion);

        if (latestInfo.isCompatible) {
            latestInfo.isLatestVersion = true;
            return latestInfo;
        } else {
            // Look at earlier versions (skipping very latest version since we already checked it)
            for (i--; i >= 0; i--) {
                var compatInfo = getCompatibilityInfoForVersion(entry.versions[i], apiVersion);
                if (compatInfo.isCompatible) {
                    compatInfo.isLatestVersion = false;
                    compatInfo.requiresNewer = latestInfo.requiresNewer;
                    return compatInfo;
                }
            }

            // No version is compatible, so just return info for the latest version
            return latestInfo;
        }
    }

getCompatibilityInfoForVersion

Determines if the given versions[] entry is compatible with the given Brackets API version, and if not specifies why.

extVersion Object
apiVersion string
Returns: {isCompatible: boolean,requiresNewer: ?boolean,compatibleVersion: ?string}
    function getCompatibilityInfoForVersion(extVersion, apiVersion) {
        var requiredVersion = (extVersion.brackets || (extVersion.engines && extVersion.engines.brackets)),
            result = {};
        result.isCompatible = !requiredVersion || semver.satisfies(apiVersion, requiredVersion);
        if (result.isCompatible) {
            result.compatibleVersion = extVersion.version;
        } else {
            // Find out reason for incompatibility
            if (requiredVersion.charAt(0) === '<') {
                result.requiresNewer = false;
            } else if (requiredVersion.charAt(0) === '>') {
                result.requiresNewer = true;
            } else if (requiredVersion.charAt(0) === "~") {
                var compareVersion = requiredVersion.slice(1);
                // Need to add .0s to this style of range in order to compare (since valid version
                // numbers must have major/minor/patch).
                if (compareVersion.match(/^[0-9]+$/)) {
                    compareVersion += ".0.0";
                } else if (compareVersion.match(/^[0-9]+\.[0-9]+$/)) {
                    compareVersion += ".0";
                }
                result.requiresNewer = semver.lt(apiVersion, compareVersion);
            }
        }
        return result;
    }
Public API

getExtensionURL

Given an extension id and version number, returns the URL for downloading that extension from the repository. Does not guarantee that the extension exists at that URL.

id string
The extension's name from the metadata.
version string
The version to download.
Returns: string
The URL to download the extension from.
    function getExtensionURL(id, version) {
        return StringUtils.format(brackets.config.extension_url, id, version);
    }
Public API

hasExtensionsToDisable

Returns true if there are any extensions marked for disabling.

Returns: boolean
true if there are extensions to disable
    function hasExtensionsToDisable() {
        return Object.keys(_idsToDisable).length > 0;
    }
Public API

hasExtensionsToRemove

Returns true if there are any extensions marked for removal.

Returns: boolean
true if there are extensions to remove
    function hasExtensionsToRemove() {
        return Object.keys(_idsToRemove).length > 0;
    }
Public API

hasExtensionsToUpdate

Returns true if there are any extensions marked for update.

Returns: boolean
true if there are extensions to update
    function hasExtensionsToUpdate() {
        return Object.keys(_idsToUpdate).length > 0;
    }
Public API

isMarkedForDisabling

Returns true if an extension is mark for disabling.

id string
The id of the extension to check.
Returns: boolean
true if it's been mark for disabling, false otherwise.
    function isMarkedForDisabling(id) {
        return !!(_idsToDisable[id]);
    }
Public API

isMarkedForRemoval

Returns true if an extension is marked for removal.

id string
The id of the extension to check.
Returns: boolean
true if it's been marked for removal, false otherwise.
    function isMarkedForRemoval(id) {
        return !!(_idsToRemove[id]);
    }
Public API

isMarkedForUpdate

Returns true if an extension is marked for update.

id string
The id of the extension to check.
Returns: boolean
true if it's been marked for update, false otherwise.
    function isMarkedForUpdate(id) {
        return !!(_idsToUpdate[id]);
    }
Private

loadTheme

id string
of the theme extension to load
    function loadTheme(id) {
        var extension = extensions[id];
        if (extension.installInfo && extension.installInfo.metadata && extension.installInfo.metadata.theme) {
            ThemeManager.loadPackage(extension.installInfo);
        }
    }
Public API

markForDisabling

Marks an extension for disabling later, or unmarks an extension previously marked.

id string
The id of the extension
mark boolean
Whether to mark or unmark the extension.
    function markForDisabling(id, mark) {
        if (mark) {
            _idsToDisable[id] = true;
        } else {
            delete _idsToDisable[id];
        }
        exports.trigger("statusChange", id);
    }
Public API

markForRemoval

Marks an extension for later removal, or unmarks an extension previously marked.

id string
The id of the extension to mark for removal.
mark boolean
Whether to mark or unmark it.
    function markForRemoval(id, mark) {
        if (mark) {
            _idsToRemove[id] = true;
        } else {
            delete _idsToRemove[id];
        }
        exports.trigger("statusChange", id);
    }
Public API

remove

Removes the installed extension with the given id.

id string
The id of the extension to remove.
Returns: $.Promise
A promise that's resolved when the extension is removed or rejected with an error if there's a problem with the removal.
    function remove(id) {
        var result = new $.Deferred();
        if (extensions[id] && extensions[id].installInfo) {
            Package.remove(extensions[id].installInfo.path)
                .done(function () {
                    extensions[id].installInfo = null;
                    result.resolve();
                    exports.trigger("statusChange", id);
                })
                .fail(function (err) {
                    result.reject(err);
                });
        } else {
            result.reject(StringUtils.format(Strings.EXTENSION_NOT_INSTALLED, id));
        }
        return result.promise();
    }
Public API

removeMarkedExtensions

Removes extensions previously marked for removal.

Returns: $.Promise
A promise that's resolved when all extensions are removed, or rejected if one or more extensions can't be removed. When rejected, the argument will be an array of error objects, each of which contains an "item" property with the id of the failed extension and an "error" property with the actual error.
    function removeMarkedExtensions() {
        return Async.doInParallel_aggregateErrors(
            Object.keys(_idsToRemove),
            function (id) {
                return remove(id);
            }
        );
    }
Public API

removeUpdate

Removes the mark for an extension to be updated on restart. Also deletes the downloaded package file.

id string
The id of the extension for which the update is being removed
    function removeUpdate(id) {
        var installationResult = _idsToUpdate[id];
        if (!installationResult) {
            return;
        }
        if (installationResult.localPath && !installationResult.keepFile) {
            FileSystem.getFileForPath(installationResult.localPath).unlink();
        }
        delete _idsToUpdate[id];
        exports.trigger("statusChange", id);
    }
Private

synchronizeEntry

id string
of the extension to synchronize
    function synchronizeEntry(id) {
        var entry = extensions[id];

        // Do nothing if we only have one set of data
        if (!entry || !entry.installInfo || !entry.registryInfo) {
            return;
        }

        entry.installInfo.owner = entry.registryInfo.owner;

        // Assume false
        entry.installInfo.updateAvailable   = false;
        entry.registryInfo.updateAvailable  = false;
        entry.installInfo.updateCompatible  = false;
        entry.registryInfo.updateCompatible = false;

        var currentVersion = entry.installInfo.metadata ? entry.installInfo.metadata.version : null;
        if (currentVersion && semver.lt(currentVersion, entry.registryInfo.metadata.version)) {
            // Note: available update may still be incompatible; we check for this when rendering the Update button in ExtensionManagerView._renderItem()
            entry.registryInfo.updateAvailable  = true;
            entry.installInfo.updateAvailable   = true;
            // Calculate updateCompatible to check if there's an update for current version of Brackets
            var lastCompatibleVersionInfo = _.findLast(entry.registryInfo.versions, function (versionInfo) {
                return !versionInfo.brackets || semver.satisfies(brackets.metadata.apiVersion, versionInfo.brackets);
            });
            if (lastCompatibleVersionInfo && lastCompatibleVersionInfo.version && semver.lt(currentVersion, lastCompatibleVersionInfo.version)) {
                entry.installInfo.updateCompatible        = true;
                entry.registryInfo.updateCompatible       = true;
                entry.installInfo.lastCompatibleVersion   = lastCompatibleVersionInfo.version;
                entry.registryInfo.lastCompatibleVersion  = lastCompatibleVersionInfo.version;
            }
        }

        exports.trigger("registryUpdate", id);
    }
Public API

unmarkAllForDisabling

Unmarks all the extensions that have been marked for disabling.

    function unmarkAllForDisabling() {
        _idsToDisable = {};
    }
Public API

unmarkAllForRemoval

Unmarks all extensions marked for removal.

    function unmarkAllForRemoval() {
        _idsToRemove = {};
    }
Public API

update

Updates an installed extension with the given package file.

id string
of the extension
packagePath string
path to the package file
keepFile optional boolean
Flag to keep extension package file, default=false
Returns: $.Promise
A promise that's resolved when the extension is updated or rejected with an error if there's a problem with the update.
    function update(id, packagePath, keepFile) {
        return Package.installUpdate(packagePath, id).done(function () {
            if (!keepFile) {
                FileSystem.getFileForPath(packagePath).unlink();
            }
        });
    }
Public API

updateExtensions

Updates extensions previously marked for update.

Returns: $.Promise
A promise that's resolved when all extensions are updated, or rejected if one or more extensions can't be updated. When rejected, the argument will be an array of error objects, each of which contains an "item" property with the id of the failed extension and an "error" property with the actual error.
    function updateExtensions() {
        return Async.doInParallel_aggregateErrors(
            Object.keys(_idsToUpdate),
            function (id) {
                var installationResult = _idsToUpdate[id];
                return update(installationResult.name, installationResult.localPath, installationResult.keepFile);
            }
        );
    }
Public API

updateFromDownload

If a downloaded package appears to be an update, mark the extension for update. If an extension was previously marked for removal, marking for update will turn off the removal mark.

installationResult Object
info about the install provided by the Package.download function
    function updateFromDownload(installationResult) {
        if (installationResult.keepFile === undefined) {
            installationResult.keepFile = false;
        }

        var installationStatus = installationResult.installationStatus;
        if (installationStatus === Package.InstallationStatuses.ALREADY_INSTALLED ||
                installationStatus === Package.InstallationStatuses.NEEDS_UPDATE ||
                installationStatus === Package.InstallationStatuses.SAME_VERSION ||
                installationStatus === Package.InstallationStatuses.OLDER_VERSION) {
            var id = installationResult.name;
            delete _idsToRemove[id];
            _idsToUpdate[id] = installationResult;
            exports.trigger("statusChange", id);
        }
    }