mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Adapt JsExtensionsLoader to allow it to be used from Node.js
This commit is contained in:
@@ -200,5 +200,5 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function(extension) { return []; },
|
||||
runExtensionSanityTests: function(gd, extension) { return []; },
|
||||
};
|
||||
|
@@ -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();
|
||||
|
@@ -213,7 +213,7 @@ module.exports = {
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function(extension) {
|
||||
runExtensionSanityTests: function(gd, extension) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
@@ -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
|
||||
}
|
@@ -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),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
@@ -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,
|
||||
}
|
@@ -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;
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
3
newIDE/app/src/JsExtensionsLoader/README.md
Normal file
3
newIDE/app/src/JsExtensionsLoader/README.md
Normal 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.
|
@@ -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,
|
||||
}
|
@@ -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;
|
||||
}
|
||||
};
|
||||
|
@@ -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,
|
||||
}
|
@@ -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;
|
Reference in New Issue
Block a user