Adapt JsExtensionsLoader to allow it to be used from Node.js

This commit is contained in:
Florian Rival
2018-08-24 15:14:42 +01:00
parent fe7d66dc1c
commit 303a489a41
12 changed files with 146 additions and 112 deletions

View File

@@ -200,5 +200,5 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function(extension) { return []; },
runExtensionSanityTests: function(gd, extension) { return []; },
};

View File

@@ -327,12 +327,12 @@ module.exports = {
* and setting the property to a given value.
*
* If you don't have any tests, you can simply return an empty array like this:
* `runExtensionSanityTests: function(extension) { return []; }`
* `runExtensionSanityTests: function(gd, extension) { return []; }`
*
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function(extension) {
runExtensionSanityTests: function(gd, extension) {
const dummyBehavior = extension
.getBehaviorMetadata("MyDummyExtension::DummyBehavior")
.get();

View File

@@ -213,7 +213,7 @@ module.exports = {
return extension;
},
runExtensionSanityTests: function(extension) {
runExtensionSanityTests: function(gd, extension) {
return [];
}
};

View File

@@ -1,6 +1,6 @@
// @flow
// Note: this file don't use export/imports nor Flow to allow its usage from Node.js
import optionalRequire from '../../Utils/OptionalRequire.js';
const optionalRequire = require('../../Utils/OptionalRequire.js');
const electron = optionalRequire('electron');
const app = electron ? electron.remote.app : null;
const fs = optionalRequire('fs');
@@ -9,16 +9,16 @@ const process = optionalRequire('process');
var isDarwin = process && /^darwin/.test(process.platform);
const tryPath = (
path: string,
onExists: string => void,
onNoAccess: Function
path/*: string*/,
onExists/*: string => void*/,
onNoAccess/*: Function*/
) =>
fs.access(path, fs.constants.R_OK, err => {
if (!err) onExists(path);
else onNoAccess();
});
export const findGDJS = (cb: (?string) => void) => {
const findGDJS = (cb/*: (?string) => void*/) => {
if (!path || !process || !fs) return '';
const appPath = app ? app.getAppPath() : process.cwd();
@@ -47,3 +47,7 @@ export const findGDJS = (cb: (?string) => void) => {
});
});
};
module.exports = {
findGDJS
}

View File

@@ -27,7 +27,7 @@ export default class BrowserJsExtensionsLoader implements JsExtensionsLoader {
return Promise.resolve(
jsExtensions.map(({ name, extensionModule }) => ({
extensionModulePath: 'internal-extension://' + name,
result: loadExtension(gd.JsPlatform.get(), extensionModule),
result: loadExtension(gd, gd.JsPlatform.get(), extensionModule),
}))
);
}

View File

@@ -1,13 +1,13 @@
// @flow
import { findGDJS } from '../Export/LocalExporters/LocalGDJSFinder';
import optionalRequire from '../Utils/OptionalRequire';
import Window from '../Utils/Window';
// Note: this file does not use export/imports nor Flow to allow its usage from Node.js
const { findGDJS } = require('../Export/LocalExporters/LocalGDJSFinder');
const optionalRequire = require('../Utils/OptionalRequire');
const path = optionalRequire('path');
const fs = optionalRequire('fs');
const checkIfPathHasJsExtensionModule = (
extensionFolderPath
): Promise<?string> => {
) => {
return new Promise(resolve => {
const jsExtensionModulePath = path.join(
extensionFolderPath,
@@ -23,9 +23,9 @@ const checkIfPathHasJsExtensionModule = (
});
};
export const findJsExtensionModules = (): Promise<any> => {
const findJsExtensionModules = ({ filterExamples }) => {
return new Promise((resolve, reject) => {
findGDJS((gdjsRoot: ?string) => {
findGDJS((gdjsRoot) => {
if (!gdjsRoot) {
return reject();
}
@@ -40,7 +40,7 @@ export const findJsExtensionModules = (): Promise<any> => {
}
const filteredExtensionFolders = extensionFolders.filter(folder => {
if (Window.isDev()) return true;
if (!filterExamples) return true;
return folder.indexOf("Example") === -1;
})
@@ -58,3 +58,7 @@ export const findJsExtensionModules = (): Promise<any> => {
});
});
};
module.exports = {
findJsExtensionModules,
}

View File

@@ -1,65 +1,61 @@
// @flow
import {
type JsExtensionsLoader,
type ExtensionLoadingResult,
loadExtension,
} from '.';
import optionalRequire from '../Utils/OptionalRequire';
import { findJsExtensionModules } from './LocalJsExtensionsFinder';
const gd = global.gd;
// Note: this file don't use export/imports nor Flow to allow its usage from Node.js
const { loadExtension } = require('.');
const optionalRequire = require('../Utils/OptionalRequire');
const { findJsExtensionModules } = require('./LocalJsExtensionsFinder');
/**
* Loader that will find all JS extensions declared in GDJS/Runtime/Extensions/xxx/JsExtension.js.
* If you add a new extension and also want it to be available for the web-app version, add it in
* BrowserJsExtensionsLoader.js
*/
export default class LocalJsExtensionsLoader implements JsExtensionsLoader {
loadAllExtensions(): Promise<
Array<{ extensionModulePath: string, result: ExtensionLoadingResult }>
> {
return findJsExtensionModules().then(
extensionModulePaths => {
return Promise.all(
extensionModulePaths.map((extensionModulePath) => {
let extensionModule = null;
try {
extensionModule = optionalRequire(extensionModulePath, {
rethrowException: true,
});
} catch (ex) {
module.exports = function makeExtensionloader({ gd, filterExamples }) {
return {
loadAllExtensions: () => {
return findJsExtensionModules({ filterExamples }).then(
extensionModulePaths => {
return Promise.all(
extensionModulePaths.map(extensionModulePath => {
let extensionModule = null;
try {
extensionModule = optionalRequire(extensionModulePath, {
rethrowException: true,
});
} catch (ex) {
return {
extensionModulePath,
result: {
message:
'Unable to import extension. Please check for any syntax error or error that would prevent it from being run.',
error: true,
rawError: ex,
},
};
}
if (extensionModule) {
return {
extensionModulePath,
result: loadExtension(gd, gd.JsPlatform.get(), extensionModule),
};
}
return {
extensionModulePath,
result: {
message:
'Unable to import extension. Please check for any syntax error or error that would prevent it from being run.',
error: true,
rawError: ex,
message:
'Unknown error. Please check for any syntax error or error that would prevent it from being run.',
},
};
}
if (extensionModule) {
return {
extensionModulePath,
result: loadExtension(gd.JsPlatform.get(), extensionModule),
};
}
return {
extensionModulePath,
result: {
error: true,
message:
'Unknown error. Please check for any syntax error or error that would prevent it from being run.',
},
};
})
);
},
err => {
console.error(`Unable to find JS extensions modules`);
throw err;
}
);
}
}
})
);
},
err => {
console.error(`Unable to find JS extensions modules`);
throw err;
}
);
},
};
};

View File

@@ -0,0 +1,3 @@
This directory contains the extension loader for Node.js/Electron (`LocalJsExtensionsLoader`) or the Browser (`BrowserJsExtensionsLoader`).
`LocalJsExtensionsLoader` is loading extensions from GDJS Runtime folder. Be sure to launch `node import-GDJS-Runtime.js` from `newIDE/app/scripts` if you made any changes in an extension.

View File

@@ -1,34 +1,34 @@
// @flow
import some from 'lodash/some';
const gd = global.gd;
// Note: this file does not use export/imports nor Flow to allow its usage from Node.js
const some = require('lodash/some');
const t = _ => _; //TODO: Implement support for i18n for extensions.
export type JsExtensionModule = {
createExtension(t, gd): gdPlatformExtension,
runExtensionSanityTests(extension: gdPlatformExtension): Array<string>,
};
// export type JsExtensionModule = {
// createExtension(t, gd): gdPlatformExtension,
// runExtensionSanityTests(extension: gdPlatformExtension): Array<string>,
// };
export type ExtensionLoadingResult = {
error: boolean,
message: string,
dangerous?: boolean,
rawError?: any,
};
// export type ExtensionLoadingResult = {
// error: boolean,
// message: string,
// dangerous?: boolean,
// rawError?: any,
// };
export interface JsExtensionsLoader {
loadAllExtensions(): Promise<
Array<{ extensionModulePath: string, result: ExtensionLoadingResult }>
>,
}
// export interface JsExtensionsLoader {
// loadAllExtensions(): Promise<
// Array<{ extensionModulePath: string, result: ExtensionLoadingResult }>
// >,
// }
/**
* Run extensions tests and check for any non-empty results.
*/
export const runExtensionSanityTests = (
extension: gdPlatformExtension,
jsExtensionModule: JsExtensionModule
): ExtensionLoadingResult => {
const runExtensionSanityTests = (
gd,
extension/*: gdPlatformExtension*/,
jsExtensionModule/*: JsExtensionModule*/
)/*: ExtensionLoadingResult*/ => {
if (!jsExtensionModule.runExtensionSanityTests) {
return {
error: true,
@@ -37,7 +37,7 @@ export const runExtensionSanityTests = (
};
}
const testResults = jsExtensionModule.runExtensionSanityTests(extension);
const testResults = jsExtensionModule.runExtensionSanityTests(gd, extension);
if (some(testResults)) {
return {
error: true,
@@ -56,10 +56,11 @@ export const runExtensionSanityTests = (
* Load an extension from the specified JavaScript module, which is supposed
* to contain a "createExtension" function returning a gd.PlatformExtension.
*/
export const loadExtension = (
platform: gdPlatform,
jsExtensionModule: JsExtensionModule
): ExtensionLoadingResult => {
const loadExtension = (
gd,
platform/*: gdPlatform*/,
jsExtensionModule/*: JsExtensionModule*/
)/*: ExtensionLoadingResult*/ => {
if (!jsExtensionModule.createExtension) {
return {
message:
@@ -87,7 +88,7 @@ export const loadExtension = (
}
try {
const testsResult = runExtensionSanityTests(extension, jsExtensionModule);
const testsResult = runExtensionSanityTests(gd, extension, jsExtensionModule);
if (testsResult.error) {
extension.delete();
return testsResult;
@@ -109,3 +110,8 @@ export const loadExtension = (
error: false,
};
};
module.exports = {
runExtensionSanityTests,
loadExtension,
}

View File

@@ -18,7 +18,8 @@ import LocalProjectOpener from './ProjectsStorage/LocalProjectOpener';
import LocalPreviewLauncher from './Export/LocalExporters/LocalPreviewLauncher';
import { getLocalExporters } from './Export/LocalExporters';
import ElectronEventsBridge from './MainFrame/ElectronEventsBridge';
import LocalJsExtensionsLoader from './JsExtensionsLoader/LocalJsExtensionsLoader';
import { makeExtensionloader } from './JsExtensionsLoader/LocalJsExtensionsLoader';
const gd = global.gd;
export const create = (authentification: Authentification) => {
Window.setUpContextMenu();
@@ -61,12 +62,15 @@ export const create = (authentification: Authentification) => {
resourceSources={localResourceSources}
resourceExternalEditors={localResourceExternalEditors}
authentification={authentification}
extensionsLoader={new LocalJsExtensionsLoader()}
extensionsLoader={makeExtensionloader({
gd,
filterExamples: !Window.isDev(),
})}
initialPathsOrURLsToOpen={appArguments['_']}
/>
</ElectronEventsBridge>
);
);
}
return app;
}
};

View File

@@ -1,4 +1,4 @@
export const mapFor = (start, end, func) => {
const mapFor = (start, end, func) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(func(i));
@@ -6,7 +6,7 @@ export const mapFor = (start, end, func) => {
return result;
};
export const mapReverseFor = (start, end, func) => {
const mapReverseFor = (start, end, func) => {
const result = [];
for (let i = end - 1; i >= start; i--) {
result.push(func(i));
@@ -14,6 +14,12 @@ export const mapReverseFor = (start, end, func) => {
return result;
};
export const mapVector = (cppVector, func) => {
const mapVector = (cppVector, func) => {
return mapFor(0, cppVector.size(), i => func(cppVector.at(i), i));
};
module.exports = {
mapFor,
mapReverseFor,
mapVector,
}

View File

@@ -1,19 +1,30 @@
// Note: this file don't use export/imports to allow its usage from Node.js
/**
* Allow to require a Node.js/npm module without having it bundled by webpack.
* This means that this module will only be available when running on Electron.
* When running without Electron, `null` will be returned.
* This means that this module will only be available when running on Electron or Node.js.
* When running without Electron or Node.js, `null` will be returned.
*
* @param {string} moduleName The name of the module. For example: `fs`.
*/
export default function optionalRequire(moduleName, config = {
const optionalRequire = (moduleName, config = {
rethrowException: false,
}) {
}) => {
try {
// Avoid webpack trying to inject the module by using an expression
// and global to get the require function.
return global['require'](moduleName);
if (global.require) { // Electron/webpack
return global['require'](moduleName);
} else if (typeof require !== 'undefined') { //Node.js/CommonJS
const nodeRequire = require;
return nodeRequire(moduleName);
}
return null;
} catch (ex) {
if (config.rethrowException) throw ex;
return null;
}
}
module.exports = optionalRequire;