@@ -27148,11 +27148,13 @@ define("editor/MultiRangeInlineEditor", function (require, exports, module) {
2714827148 *
2714927149 */
2715027150
27151- /*global path*/
27151+ /*global path, jsPromise, catchToNull */
2715227152
2715327153define("extensibility/ExtensionDownloader", function (require, exports, module) {
2715427154 const EventDispatcher = require("utils/EventDispatcher"),
2715527155 ExtensionLoader = require("utils/ExtensionLoader"),
27156+ FileUtils = require("file/FileUtils"),
27157+ NodeUtils = require("utils/NodeUtils"),
2715627158 Package = require("extensibility/Package"),
2715727159 FileSystem = require("filesystem/FileSystem"),
2715827160 ZipUtils = require("utils/ZipUtils");
@@ -27234,14 +27236,97 @@ define("extensibility/ExtensionDownloader", function (require, exports, module)
2723427236 downloadCancelled[downloadId] = true;
2723527237 }
2723627238
27239+ async function _validateAndNpmInstallIfNodeExtension(nodeExtPath) {
27240+ const packageJSONFile = FileSystem.getFileForPath(path.join(nodeExtPath, "package.json"));
27241+ let packageJson = await catchToNull(jsPromise(FileUtils.readAsText(packageJSONFile)),
27242+ "package.json not found for installing extension, trying to continue "+ nodeExtPath);
27243+ try{
27244+ if(packageJson){
27245+ packageJson = JSON.parse(packageJson);
27246+ }
27247+ } catch (e) {
27248+ console.error("Error parsing package json for extension", nodeExtPath, e);
27249+ return null; // let it flow, we are only concerned of node extensions
27250+ }
27251+ if(!packageJson || !packageJson.nodeConfig || !packageJson.nodeConfig.main){
27252+ // legacy extensions can be loaded with no package.json
27253+ // else if no node config, or node main is not defined, we just treat it as a non node extension
27254+ return null;
27255+ }
27256+ if(packageJson.nodeConfig.nodeIsRequired && !Phoenix.isNativeApp) {
27257+ return "Extension can only be installed in native builds!";
27258+ }
27259+ let nodeMainFile = path.join(nodeExtPath, packageJson.nodeConfig.main);
27260+ let file = FileSystem.getFileForPath(nodeMainFile);
27261+ let isExists = await file.existsAsync();
27262+ if(!isExists){
27263+ console.error("Extension cannot be installed; could not find node main file: ",
27264+ nodeMainFile, packageJson.nodeConfig.main);
27265+ return "Extension is broken, (Err: node main file not found)";
27266+ }
27267+
27268+ let npmInstallFolder = packageJson.nodeConfig.npmInstall;
27269+ if(!npmInstallFolder) {
27270+ return null;
27271+ }
27272+ npmInstallFolder = path.join(nodeExtPath, packageJson.nodeConfig.npmInstall);
27273+ const nodeModulesFolder = path.join(npmInstallFolder, "node_modules");
27274+ let directory = FileSystem.getDirectoryForPath(npmInstallFolder);
27275+ isExists = await directory.existsAsync();
27276+ if(!isExists){
27277+ console.error("Extension cannot be installed; could not find folder to run npm install: ",
27278+ npmInstallFolder);
27279+ return "Extension is broken, (Err: node source folder not found)";
27280+ }
27281+
27282+ const nodePackageJson = path.join(npmInstallFolder, "package.json");
27283+ let nodePackageFile = FileSystem.getFileForPath(nodePackageJson);
27284+ isExists = await nodePackageFile.existsAsync();
27285+ if(!isExists){
27286+ console.error("Extension cannot be installed; could not find package.json file to npm install in: ",
27287+ npmInstallFolder);
27288+ return "Extension is broken, (Err: it's node package.json not found)";
27289+ }
27290+
27291+ directory = FileSystem.getDirectoryForPath(nodeModulesFolder);
27292+ isExists = await directory.existsAsync();
27293+ if(isExists) {
27294+ console.error("Could not install extension as the extension has node_modules folder in" +
27295+ " the package", nodeModulesFolder, "Extensions that defines a nodeConfig.npmInstall" +
27296+ " path should not package node_modules!");
27297+ return "Extension is broken. (Err: cannot npm install inside extension folder" +
27298+ " as it already has node_modules)";
27299+ }
27300+ const npmInstallPlatformPath = Phoenix.fs.getTauriPlatformPath(npmInstallFolder);
27301+ return NodeUtils._npmInstallInFolder(npmInstallPlatformPath);
27302+ }
27303+
2723727304 function install(path, destinationDirectory, config) {
2723827305 const d = new $.Deferred();
27239- // if we reached here in phoenix, install succeded
27240- d.resolve({
27241- name: _getExtensionName(config.nameHint),
27242- installationStatus: Package.InstallationStatuses.INSTALLED,
27243- installedTo: path
27244- });
27306+ // if we reached here in phoenix, install succeeded
27307+ _validateAndNpmInstallIfNodeExtension(path)
27308+ .then(validationErr =>{
27309+ if(validationErr) {
27310+ d.resolve({
27311+ name: _getExtensionName(config.nameHint),
27312+ installationStatus: Package.InstallationStatuses.FAILED,
27313+ errors: [validationErr]
27314+ });
27315+ return;
27316+ }
27317+ d.resolve({
27318+ name: _getExtensionName(config.nameHint),
27319+ installationStatus: Package.InstallationStatuses.INSTALLED,
27320+ installedTo: path
27321+ });
27322+ }).catch(err=>{
27323+ console.error("Error installing extension", err);
27324+ d.resolve({
27325+ name: _getExtensionName(config.nameHint),
27326+ installationStatus: Package.InstallationStatuses.FAILED,
27327+ errors: ["Error installing extension"]
27328+ });
27329+ });
2724527330 return d.promise();
2724627331 }
2724727332
@@ -27543,6 +27628,30 @@ define("extensibility/ExtensionManager", function (require, exports, module) {
2754327628 });
2754427629 }
2754527630
27631+ /**
27632+ * Filters out extensions that needs node in the browser from being listed.
27633+ *
27634+ * @param {Object} registry - The registry object to filter
27635+ * @return {Object} - The filtered registry object
27636+ * @private
27637+ */
27638+ function _filterIncompatibleEntries(registry) {
27639+ let filteredRegistry = {};
27640+ for(let registryKey of Object.keys(registry)){
27641+ const registryEntry = registry[registryKey];
27642+ let nodeNeeded = false;
27643+ if(registryEntry.metadata && registryEntry.metadata.nodeConfig &&
27644+ registryEntry.metadata.nodeConfig.nodeIsRequired) {
27645+ nodeNeeded = true;
27646+ }
27647+ if(nodeNeeded && !Phoenix.isNativeApp) {
27648+ continue;
27649+ }
27650+ filteredRegistry[registryKey] = registryEntry;
27651+ }
27652+ return filteredRegistry;
27653+ }
27654+
2754627655 /**
2754727656 * Downloads the registry of Brackets extensions and stores the information in our
2754827657 * extension info.
@@ -27565,6 +27674,7 @@ define("extensibility/ExtensionManager", function (require, exports, module) {
2756527674 cache: false
2756627675 })
2756727676 .done(function (registry) {
27677+ registry = _filterIncompatibleEntries(registry);
2756827678 localStorage.setItem(EXTENSION_REGISTRY_LOCAL_STORAGE_VERSION_KEY, newVersion);
2756927679 localStorage.setItem(EXTENSION_REGISTRY_LOCAL_STORAGE_KEY, JSON.stringify(registry));
2757027680 if(!pendingDownloadRegistry.alreadyResolvedFromCache){
@@ -27595,7 +27705,9 @@ define("extensibility/ExtensionManager", function (require, exports, module) {
2759527705 // resolve for ui responsiveness and then check for updates.
2759627706 setTimeout(()=>{
2759727707 Metrics.countEvent(Metrics.EVENT_TYPE.EXTENSIONS, "registry", "cachedUse");
27598- _populateExtensions(JSON.parse(registryJson));
27708+ let registry = JSON.parse(registryJson);
27709+ registry = _filterIncompatibleEntries(registry);
27710+ _populateExtensions(registry);
2759927711 pendingDownloadRegistry.resolve();
2760027712 }, 0);
2760127713 pendingDownloadRegistry.alreadyResolvedFromCache = true;
@@ -137882,7 +137994,7 @@ define("utils/ExtensionInterface", function (require, exports, module) {
137882137994 * extension root.
137883137995 */
137884137996// jshint ignore: start
137885- /*global logger, Phoenix */
137997+ /*global logger, path */
137886137998/*eslint-env es6*/
137887137999/*eslint no-console: 0*/
137888138000/*eslint strict: ["error", "global"]*/
@@ -137900,6 +138012,7 @@ define("utils/ExtensionLoader", function (require, exports, module) {
137900138012 ExtensionUtils = require("utils/ExtensionUtils"),
137901138013 ThemeManager = require("view/ThemeManager"),
137902138014 UrlParams = require("utils/UrlParams").UrlParams,
138015+ NodeUtils = require("utils/NodeUtils"),
137903138016 PathUtils = require("thirdparty/path-utils/path-utils"),
137904138017 DefaultExtensionsList = JSON.parse(require("text!extensions/default/DefaultExtensions.json"))
137905138018 .defaultExtensionsList;
@@ -138059,17 +138172,24 @@ define("utils/ExtensionLoader", function (require, exports, module) {
138059138172 }
138060138173 const savedFSlib = window.fs;
138061138174
138175+ function _loadNodeExtension(name, extensionMainPath, nodeConfig) {
138176+ const mainPlatformPath = Phoenix.fs.getTauriPlatformPath(extensionMainPath);
138177+ console.log("Loading node extension for " + name, extensionMainPath, ":", mainPlatformPath, nodeConfig);
138178+ NodeUtils._loadNodeExtensionModule(mainPlatformPath); // let load errors get reported to bugsnag
138179+ }
138180+
138062138181 /**
138063138182 * Loads the extension module that lives at baseUrl into its own Require.js context
138064138183 *
138065138184 * @param {!string} name, used to identify the extension
138066138185 * @param {!{baseUrl: string}} config object with baseUrl property containing absolute path of extension
138067- * @param {!string} entryPoint, name of the main js file to load
138186+ * @param {string} entryPoint name of the main js file to load
138187+ * @param {Object} metadata
138068138188 * @return {!$.Promise} A promise object that is resolved when the extension is loaded, or rejected
138069138189 * if the extension fails to load or throws an exception immediately when loaded.
138070138190 * (Note: if extension contains a JS syntax error, promise is resolved not rejected).
138071138191 */
138072- function loadExtensionModule(name, config, entryPoint) {
138192+ function loadExtensionModule(name, config, entryPoint, metadata ) {
138073138193 let extensionConfig = {
138074138194 context: name,
138075138195 baseUrl: config.baseUrl,
@@ -138094,7 +138214,24 @@ define("utils/ExtensionLoader", function (require, exports, module) {
138094138214 // Create new RequireJS context and load extension entry point
138095138215 var extensionRequire = brackets.libRequire.config(mergedConfig),
138096138216 extensionRequireDeferred = new $.Deferred();
138097-
138217+ if(!isDefaultExtensionModule && config.nativeDir && metadata.nodeConfig){
138218+ if(!Phoenix.isNativeApp && metadata.nodeConfig.nodeIsRequired) {
138219+ extensionRequireDeferred.reject(
138220+ new Error(`Extension ${name} cannot be loaded in browser as it needs node(nodeConfig.nodeIsRequired:true)`));
138221+ return extensionRequireDeferred.promise();
138222+ }
138223+ if(Phoenix.isNativeApp) {
138224+ if(!metadata.nodeConfig.main){
138225+ extensionRequireDeferred.reject(
138226+ new Error(`Extension ${name} doesnt specify a main file(nodeConfig.main) in package.json!`));
138227+ return extensionRequireDeferred.promise();
138228+ }
138229+ _loadNodeExtension(name, path.join(config.nativeDir, metadata.nodeConfig.main),
138230+ metadata.nodeConfig);
138231+ } else {
138232+ console.log(`Extension ${name} optionally needs node. Node not loaded in browser.`);
138233+ }
138234+ }
138098138235 contexts[name] = extensionRequire;
138099138236 extensionRequire([entryPoint], extensionRequireDeferred.resolve, extensionRequireDeferred.reject);
138100138237
@@ -138189,7 +138326,7 @@ define("utils/ExtensionLoader", function (require, exports, module) {
138189138326 }
138190138327
138191138328 if (!metadata.disabled) {
138192- return loadExtensionModule(name, config, entryPoint);
138329+ return loadExtensionModule(name, config, entryPoint, metadata );
138193138330 }
138194138331 return new $.Deferred().reject("disabled").promise();
138195138332
@@ -138308,9 +138445,10 @@ define("utils/ExtensionLoader", function (require, exports, module) {
138308138445 }
138309138446
138310138447 Async.doInParallel(extensions, function (item) {
138311- var extConfig = {
138448+ const extConfig = {
138312138449 // we load user installed extensions in file system from our virtual/asset server URL
138313138450 baseUrl: Phoenix.VFS.getVirtualServingURLForPath(directory + "/" + item),
138451+ nativeDir: directory + "/" + item,
138314138452 paths: {}
138315138453 };
138316138454 console.log("Loading Extension from virtual fs: ", extConfig);
@@ -138371,7 +138509,8 @@ define("utils/ExtensionLoader", function (require, exports, module) {
138371138509 function loadExtensionFromNativeDirectory(directory) {
138372138510 logger.leaveTrail("loading custom extension from path: " + directory);
138373138511 const extConfig = {
138374- baseUrl: Phoenix.VFS.getVirtualServingURLForPath(directory.replace(/\/$/, ""))
138512+ baseUrl: Phoenix.VFS.getVirtualServingURLForPath(directory.replace(/\/$/, "")),
138513+ nativeDir: directory
138375138514 };
138376138515 return loadExtension("ext" + directory.replace("/", "-"), // /fs/user/extpath to ext-fs-user-extpath
138377138516 extConfig, 'main');
@@ -140758,7 +140897,7 @@ define("utils/NodeUtils", function (require, exports, module) {
140758140897 * updates the localized strings in brackets `Strings` to node.
140759140898 * @return {Promise<boolean>} Promise resolves to true if strings was updated in node, else false(in browser.)
140760140899 */
140761- async function updateNodeLocaleStrings () {
140900+ async function _updateNodeLocaleStrings () {
140762140901 if(!Phoenix.isNativeApp) {
140763140902 // this does nothing in browser builds.
140764140903 return false;
@@ -140790,14 +140929,32 @@ define("utils/NodeUtils", function (require, exports, module) {
140790140929 return utilsConnector.execPeer("openUrlInBrowser", {url, browserName});
140791140930 }
140792140931
140932+ async function _loadNodeExtensionModule(moduleNativeDir) {
140933+ if(!Phoenix.isNativeApp) {
140934+ throw new Error("_loadNodeExtensionModule not available in browser");
140935+ }
140936+ return utilsConnector.execPeer("_loadNodeExtensionModule", {moduleNativeDir});
140937+ }
140938+
140939+ async function _npmInstallInFolder(moduleNativeDir) {
140940+ if(!Phoenix.isNativeApp) {
140941+ throw new Error("_npmInstallInFolder not available in browser");
140942+ }
140943+ return utilsConnector.execPeer("_npmInstallInFolder", {moduleNativeDir});
140944+ }
140945+
140793140946 if(NodeConnector.isNodeAvailable()) {
140794140947 // todo we need to update the strings if a user extension adds its translations. Since we dont support
140795140948 // node extensions for now, should consider when we support node extensions.
140796- updateNodeLocaleStrings ();
140949+ _updateNodeLocaleStrings ();
140797140950 }
140798140951
140952+ // private apis
140953+ exports._loadNodeExtensionModule = _loadNodeExtensionModule;
140954+ exports._npmInstallInFolder = _npmInstallInFolder;
140955+
140956+ // public apis
140799140957 exports.fetchURLText = fetchURLText;
140800- exports.updateNodeLocaleStrings = updateNodeLocaleStrings;
140801140958 exports.getPhoenixBinaryVersion = getPhoenixBinaryVersion;
140802140959 exports.getLinuxOSFlavorName = getLinuxOSFlavorName;
140803140960 exports.openUrlInBrowser = openUrlInBrowser;
0 commit comments