Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/extensions/default/DefaultExtensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"CodeFolding"
]
114 changes: 95 additions & 19 deletions src/utils/ExtensionLoader.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
* Copyright (c) 2021 - present core.ai . All rights reserved.
* Copyright (c) 2012 - 2021 Adobe Systems Incorporated. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
Expand Down Expand Up @@ -29,6 +30,11 @@
* "loadFailed" - when an extension load is unsuccessful. The second argument is the file path to the
* extension root.
*/
// jshint ignore: start
/*global fs, Phoenix*/
/*eslint-env es6*/
/*eslint no-console: 0*/
/*eslint strict: ["error", "global"]*/

define(function (require, exports, module) {

Expand Down Expand Up @@ -58,6 +64,8 @@ define(function (require, exports, module) {
*/
var contexts = {};

var pathLib = Phoenix.VFS.path;

// The native directory path ends with either "test" or "src". We need "src" to
// load the text and i18n modules.
srcPath = srcPath.replace(/\/test$/, "/src"); // convert from "test" to "src"
Expand All @@ -72,10 +80,17 @@ define(function (require, exports, module) {
});

/**
* Returns the full path to the default extensions directory.
* Returns the path to the default extensions directory relative to window.location.href
*/
function getDefaultExtensionPath() {
return FileUtils.getNativeBracketsDirectoryPath() + "/extensions/default";
return pathLib.normalize("/extensions/default");
}

/**
* Returns the full path to the development extensions directory.
*/
function getDevExtensionPath() {
return pathLib.normalize(brackets.app.getApplicationSupportDirectory() + "/extensions/dev");
}

/**
Expand All @@ -86,7 +101,7 @@ define(function (require, exports, module) {
*/
function getUserExtensionPath() {
if (brackets.app.getApplicationSupportDirectory) {
return brackets.app.getApplicationSupportDirectory() + "/extensions/user";
return pathLib.normalize(brackets.app.getApplicationSupportDirectory() + "/extensions/user");
}

return null;
Expand Down Expand Up @@ -121,13 +136,48 @@ define(function (require, exports, module) {
_initExtensionTimeout = value;
}

/**
* @private
* Loads optional requirejs-config.json file for an extension
* @param {Object} baseConfig
* @return {$.Promise}
*/
function _mergeConfigFromURL(baseConfig) {
var deferred = new $.Deferred(),
extensionConfigFile = baseConfig.baseUrl + "/requirejs-config.json";

// Optional JSON config for require.js
$.get(extensionConfigFile).done(function (extensionConfig) {
try {
// baseConfig.paths properties will override any extension config paths
_.extend(extensionConfig.paths, baseConfig.paths);

// Overwrite baseUrl, context, locale (paths is already merged above)
_.extend(extensionConfig, _.omit(baseConfig, "paths"));

deferred.resolve(extensionConfig);
} catch (err) {
// Failed to parse requirejs-config.json
deferred.reject("failed to parse requirejs-config.json");
}
}).fail(function () {
// If requirejs-config.json isn't specified, resolve with the baseConfig only
deferred.resolve(baseConfig);
});

return deferred.promise();
}

/**
* @private
* Loads optional requirejs-config.json file for an extension
* @param {Object} baseConfig
* @return {$.Promise}
*/
function _mergeConfig(baseConfig) {
if(baseConfig.baseUrl.startsWith("http://") || baseConfig.baseUrl.startsWith("https://")) {
return _mergeConfigFromURL(baseConfig);
}
var deferred = new $.Deferred(),
extensionConfigFile = FileSystem.getFileForPath(baseConfig.baseUrl + "/requirejs-config.json");

Expand Down Expand Up @@ -249,7 +299,7 @@ define(function (require, exports, module) {
var promise = new $.Deferred();

// Try to load the package.json to figure out if we are loading a theme.
ExtensionUtils.loadMetadata(config.baseUrl).always(promise.resolve);
ExtensionUtils.loadMetadata(config.baseUrl, name).always(promise.resolve);

return promise
.then(function (metadata) {
Expand Down Expand Up @@ -358,6 +408,40 @@ define(function (require, exports, module) {
return result.promise();
}

/**
* Loads All brackets default extensions from brackets base https URL.
*
* @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
*/
function loadAllDefaultExtensions() {
const extensionPath = getDefaultExtensionPath();
const href = window.location.href;
const baseUrl = href.substring(0, href.lastIndexOf("/"));
const extensionsToLoadURL = baseUrl + extensionPath + "/DefaultExtensions.json";
var result = new $.Deferred();

$.get(extensionsToLoadURL).done(function (extensionNames) {
Async.doInParallel(extensionNames, function (extensionName) {
console.log("loading default extension: ", extensionName);
var extConfig = {
baseUrl: baseUrl + extensionPath + "/" + extensionName
};
return loadExtension(extensionName, extConfig, 'main');
}).always(function () {
// Always resolve the promise even if some extensions had errors
result.resolve();
});

})
.fail(function (err) {
console.error("[Extension] Error -- could not read default extension list from" + extensionsToLoadURL);
result.reject();
});

return result.promise();

}

/**
* Loads the extension that lives at baseUrl into its own Require.js context
*
Expand Down Expand Up @@ -409,13 +493,10 @@ define(function (require, exports, module) {
if (!paths) {
params.parse();

if (params.get("reloadWithoutUserExts") === "true") {
paths = ["default"];
} else {
if (params.get("reloadWithoutUserExts") !== "true") {
paths = [
getDefaultExtensionPath(),
"dev",
getUserExtensionPath()
getUserExtensionPath(),
getDevExtensionPath()
];
}
}
Expand All @@ -431,20 +512,15 @@ define(function (require, exports, module) {
// during extension loading.
var extensionPath = getUserExtensionPath();
FileSystem.getDirectoryForPath(extensionPath).create();
FileSystem.getDirectoryForPath(getDevExtensionPath()).create();

// Create the extensions/disabled directory, too.
var disabledExtensionPath = extensionPath.replace(/\/user$/, "/disabled");
FileSystem.getDirectoryForPath(disabledExtensionPath).create();

var promise = Async.doSequentially(paths, function (item) {
var extensionPath = item;

// If the item has "/" in it, assume it is a full path. Otherwise, load
// from our source path + "/extensions/".
if (item.indexOf("/") === -1) {
extensionPath = FileUtils.getNativeBracketsDirectoryPath() + "/extensions/" + item;
}
loadAllDefaultExtensions();

var promise = Async.doSequentially(paths, function (extensionPath) {
return loadAllExtensionsInNativeDirectory(extensionPath);
}, false);

Expand Down
69 changes: 57 additions & 12 deletions src/utils/ExtensionUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,7 @@ define(function (require, exports, module) {
* @return {!string} The URL to the module's folder
**/
function getModuleUrl(module, path) {
var url = encodeURI(getModulePath(module, path));

// On Windows, $.get() fails if the url is a full pathname. To work around this,
// prepend "file:///". On the Mac, $.get() works fine if the url is a full pathname,
// but *doesn't* work if it is prepended with "file://". Go figure.
// However, the prefix "file://localhost" does work.
if (brackets.platform === "win" && url.indexOf(":") !== -1) {
url = "file:///" + url;
}

return url;
return encodeURI(getModulePath(module, path));
}

/**
Expand Down Expand Up @@ -238,7 +228,7 @@ define(function (require, exports, module) {
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
*/
function loadMetadata(folder) {
function _loadLocalMetadata(folder) {
var packageJSONFile = FileSystem.getFileForPath(folder + "/package.json"),
disabledFile = FileSystem.getFileForPath(folder + "/.disabled"),
baseName = FileUtils.getBaseName(folder),
Expand Down Expand Up @@ -287,6 +277,61 @@ define(function (require, exports, module) {
});
return result.promise();
}
/**
* Loads the package.json file in the given extension folder as well as any additional
* metadata.
*
* @param {string} baseExtensionUrl The extension folder.
* @param {?string} extensionName optional extension name
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
*/
function _loadDefaultExtensionMetadata(baseExtensionUrl, extensionName) {
var packageJSONFile = baseExtensionUrl + "/package.json";
var result = new $.Deferred();
var json = {
name: extensionName
};
$.get(packageJSONFile)
.then(function (result) {
json = result;
}).always(function () {
// if we don't have any metadata for the extension
// we should still create an empty one, so we can attach
// disabled property on it in case it's disabled
var disabled;
var defaultDisabled = PreferencesManager.get("extensions.default.disabled");
if (Array.isArray(defaultDisabled) && defaultDisabled.indexOf(extensionName) !== -1) {
console.warn("Default extension has been disabled on startup: " + baseExtensionUrl);
disabled = true;
}
json.disabled = disabled;
result.resolve(json);
});

return result.promise();
}

/**
* Loads the package.json file in the given extension folder as well as any additional
* metadata for default extensions in the source directory.
*
* If there's a .disabled file in the extension directory, then the content of package.json
* will be augmented with disabled property set to true. It will override whatever value of
* disabled might be set.
*
* @param {string} folder The extension folder/base url for default extensions.
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
*/
function loadMetadata(folder, extensionName) {
if(folder.startsWith("http://") || folder.startsWith("https://")) {
return _loadDefaultExtensionMetadata(folder, extensionName);
}
return _loadLocalMetadata(folder);
}



exports.addEmbeddedStyleSheet = addEmbeddedStyleSheet;
exports.addLinkedStyleSheet = addLinkedStyleSheet;
Expand Down