Skip to content

Commit 34b6059

Browse files
authored
Merge pull request #54 from aicore/fs
Default extensions loading fix
2 parents 52e8ff4 + 6c56c70 commit 34b6059

3 files changed

Lines changed: 155 additions & 31 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"CodeFolding"
3+
]

src/utils/ExtensionLoader.js

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
2-
* Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
2+
* Copyright (c) 2021 - present core.ai . All rights reserved.
3+
* Copyright (c) 2012 - 2021 Adobe Systems Incorporated. All rights reserved.
34
*
45
* Permission is hereby granted, free of charge, to any person obtaining a
56
* copy of this software and associated documentation files (the "Software"),
@@ -29,6 +30,11 @@
2930
* "loadFailed" - when an extension load is unsuccessful. The second argument is the file path to the
3031
* extension root.
3132
*/
33+
// jshint ignore: start
34+
/*global fs, Phoenix*/
35+
/*eslint-env es6*/
36+
/*eslint no-console: 0*/
37+
/*eslint strict: ["error", "global"]*/
3238

3339
define(function (require, exports, module) {
3440

@@ -58,6 +64,8 @@ define(function (require, exports, module) {
5864
*/
5965
var contexts = {};
6066

67+
var pathLib = Phoenix.VFS.path;
68+
6169
// The native directory path ends with either "test" or "src". We need "src" to
6270
// load the text and i18n modules.
6371
srcPath = srcPath.replace(/\/test$/, "/src"); // convert from "test" to "src"
@@ -72,10 +80,17 @@ define(function (require, exports, module) {
7280
});
7381

7482
/**
75-
* Returns the full path to the default extensions directory.
83+
* Returns the path to the default extensions directory relative to window.location.href
7684
*/
7785
function getDefaultExtensionPath() {
78-
return FileUtils.getNativeBracketsDirectoryPath() + "/extensions/default";
86+
return pathLib.normalize("/extensions/default");
87+
}
88+
89+
/**
90+
* Returns the full path to the development extensions directory.
91+
*/
92+
function getDevExtensionPath() {
93+
return pathLib.normalize(brackets.app.getApplicationSupportDirectory() + "/extensions/dev");
7994
}
8095

8196
/**
@@ -86,7 +101,7 @@ define(function (require, exports, module) {
86101
*/
87102
function getUserExtensionPath() {
88103
if (brackets.app.getApplicationSupportDirectory) {
89-
return brackets.app.getApplicationSupportDirectory() + "/extensions/user";
104+
return pathLib.normalize(brackets.app.getApplicationSupportDirectory() + "/extensions/user");
90105
}
91106

92107
return null;
@@ -121,13 +136,48 @@ define(function (require, exports, module) {
121136
_initExtensionTimeout = value;
122137
}
123138

139+
/**
140+
* @private
141+
* Loads optional requirejs-config.json file for an extension
142+
* @param {Object} baseConfig
143+
* @return {$.Promise}
144+
*/
145+
function _mergeConfigFromURL(baseConfig) {
146+
var deferred = new $.Deferred(),
147+
extensionConfigFile = baseConfig.baseUrl + "/requirejs-config.json";
148+
149+
// Optional JSON config for require.js
150+
$.get(extensionConfigFile).done(function (extensionConfig) {
151+
try {
152+
// baseConfig.paths properties will override any extension config paths
153+
_.extend(extensionConfig.paths, baseConfig.paths);
154+
155+
// Overwrite baseUrl, context, locale (paths is already merged above)
156+
_.extend(extensionConfig, _.omit(baseConfig, "paths"));
157+
158+
deferred.resolve(extensionConfig);
159+
} catch (err) {
160+
// Failed to parse requirejs-config.json
161+
deferred.reject("failed to parse requirejs-config.json");
162+
}
163+
}).fail(function () {
164+
// If requirejs-config.json isn't specified, resolve with the baseConfig only
165+
deferred.resolve(baseConfig);
166+
});
167+
168+
return deferred.promise();
169+
}
170+
124171
/**
125172
* @private
126173
* Loads optional requirejs-config.json file for an extension
127174
* @param {Object} baseConfig
128175
* @return {$.Promise}
129176
*/
130177
function _mergeConfig(baseConfig) {
178+
if(baseConfig.baseUrl.startsWith("http://") || baseConfig.baseUrl.startsWith("https://")) {
179+
return _mergeConfigFromURL(baseConfig);
180+
}
131181
var deferred = new $.Deferred(),
132182
extensionConfigFile = FileSystem.getFileForPath(baseConfig.baseUrl + "/requirejs-config.json");
133183

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

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

254304
return promise
255305
.then(function (metadata) {
@@ -358,6 +408,40 @@ define(function (require, exports, module) {
358408
return result.promise();
359409
}
360410

411+
/**
412+
* Loads All brackets default extensions from brackets base https URL.
413+
*
414+
* @return {!$.Promise} A promise object that is resolved when all extensions complete loading.
415+
*/
416+
function loadAllDefaultExtensions() {
417+
const extensionPath = getDefaultExtensionPath();
418+
const href = window.location.href;
419+
const baseUrl = href.substring(0, href.lastIndexOf("/"));
420+
const extensionsToLoadURL = baseUrl + extensionPath + "/DefaultExtensions.json";
421+
var result = new $.Deferred();
422+
423+
$.get(extensionsToLoadURL).done(function (extensionNames) {
424+
Async.doInParallel(extensionNames, function (extensionName) {
425+
console.log("loading default extension: ", extensionName);
426+
var extConfig = {
427+
baseUrl: baseUrl + extensionPath + "/" + extensionName
428+
};
429+
return loadExtension(extensionName, extConfig, 'main');
430+
}).always(function () {
431+
// Always resolve the promise even if some extensions had errors
432+
result.resolve();
433+
});
434+
435+
})
436+
.fail(function (err) {
437+
console.error("[Extension] Error -- could not read default extension list from" + extensionsToLoadURL);
438+
result.reject();
439+
});
440+
441+
return result.promise();
442+
443+
}
444+
361445
/**
362446
* Loads the extension that lives at baseUrl into its own Require.js context
363447
*
@@ -409,13 +493,10 @@ define(function (require, exports, module) {
409493
if (!paths) {
410494
params.parse();
411495

412-
if (params.get("reloadWithoutUserExts") === "true") {
413-
paths = ["default"];
414-
} else {
496+
if (params.get("reloadWithoutUserExts") !== "true") {
415497
paths = [
416-
getDefaultExtensionPath(),
417-
"dev",
418-
getUserExtensionPath()
498+
getUserExtensionPath(),
499+
getDevExtensionPath()
419500
];
420501
}
421502
}
@@ -431,20 +512,15 @@ define(function (require, exports, module) {
431512
// during extension loading.
432513
var extensionPath = getUserExtensionPath();
433514
FileSystem.getDirectoryForPath(extensionPath).create();
515+
FileSystem.getDirectoryForPath(getDevExtensionPath()).create();
434516

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

439-
var promise = Async.doSequentially(paths, function (item) {
440-
var extensionPath = item;
441-
442-
// If the item has "/" in it, assume it is a full path. Otherwise, load
443-
// from our source path + "/extensions/".
444-
if (item.indexOf("/") === -1) {
445-
extensionPath = FileUtils.getNativeBracketsDirectoryPath() + "/extensions/" + item;
446-
}
521+
loadAllDefaultExtensions();
447522

523+
var promise = Async.doSequentially(paths, function (extensionPath) {
448524
return loadAllExtensionsInNativeDirectory(extensionPath);
449525
}, false);
450526

src/utils/ExtensionUtils.js

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,7 @@ define(function (require, exports, module) {
151151
* @return {!string} The URL to the module's folder
152152
**/
153153
function getModuleUrl(module, path) {
154-
var url = encodeURI(getModulePath(module, path));
155-
156-
// On Windows, $.get() fails if the url is a full pathname. To work around this,
157-
// prepend "file:///". On the Mac, $.get() works fine if the url is a full pathname,
158-
// but *doesn't* work if it is prepended with "file://". Go figure.
159-
// However, the prefix "file://localhost" does work.
160-
if (brackets.platform === "win" && url.indexOf(":") !== -1) {
161-
url = "file:///" + url;
162-
}
163-
164-
return url;
154+
return encodeURI(getModulePath(module, path));
165155
}
166156

167157
/**
@@ -238,7 +228,7 @@ define(function (require, exports, module) {
238228
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
239229
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
240230
*/
241-
function loadMetadata(folder) {
231+
function _loadLocalMetadata(folder) {
242232
var packageJSONFile = FileSystem.getFileForPath(folder + "/package.json"),
243233
disabledFile = FileSystem.getFileForPath(folder + "/.disabled"),
244234
baseName = FileUtils.getBaseName(folder),
@@ -287,6 +277,61 @@ define(function (require, exports, module) {
287277
});
288278
return result.promise();
289279
}
280+
/**
281+
* Loads the package.json file in the given extension folder as well as any additional
282+
* metadata.
283+
*
284+
* @param {string} baseExtensionUrl The extension folder.
285+
* @param {?string} extensionName optional extension name
286+
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
287+
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
288+
*/
289+
function _loadDefaultExtensionMetadata(baseExtensionUrl, extensionName) {
290+
var packageJSONFile = baseExtensionUrl + "/package.json";
291+
var result = new $.Deferred();
292+
var json = {
293+
name: extensionName
294+
};
295+
$.get(packageJSONFile)
296+
.then(function (result) {
297+
json = result;
298+
}).always(function () {
299+
// if we don't have any metadata for the extension
300+
// we should still create an empty one, so we can attach
301+
// disabled property on it in case it's disabled
302+
var disabled;
303+
var defaultDisabled = PreferencesManager.get("extensions.default.disabled");
304+
if (Array.isArray(defaultDisabled) && defaultDisabled.indexOf(extensionName) !== -1) {
305+
console.warn("Default extension has been disabled on startup: " + baseExtensionUrl);
306+
disabled = true;
307+
}
308+
json.disabled = disabled;
309+
result.resolve(json);
310+
});
311+
312+
return result.promise();
313+
}
314+
315+
/**
316+
* Loads the package.json file in the given extension folder as well as any additional
317+
* metadata for default extensions in the source directory.
318+
*
319+
* If there's a .disabled file in the extension directory, then the content of package.json
320+
* will be augmented with disabled property set to true. It will override whatever value of
321+
* disabled might be set.
322+
*
323+
* @param {string} folder The extension folder/base url for default extensions.
324+
* @return {$.Promise} A promise object that is resolved with the parsed contents of the package.json file,
325+
* or rejected if there is no package.json with the boolean indicating whether .disabled file exists.
326+
*/
327+
function loadMetadata(folder, extensionName) {
328+
if(folder.startsWith("http://") || folder.startsWith("https://")) {
329+
return _loadDefaultExtensionMetadata(folder, extensionName);
330+
}
331+
return _loadLocalMetadata(folder);
332+
}
333+
334+
290335

291336
exports.addEmbeddedStyleSheet = addEmbeddedStyleSheet;
292337
exports.addLinkedStyleSheet = addLinkedStyleSheet;

0 commit comments

Comments
 (0)