diff --git a/extensions/extension-util.js b/extensions/extension-util.js new file mode 100644 index 000000000..87a8b65ce --- /dev/null +++ b/extensions/extension-util.js @@ -0,0 +1,32 @@ +//@extension name extension +const { Context } = extension.import('core'); + +// The 'create.commands' event is fired by CommandService +extension.on('create.commands', event => { + + // Add command to list available extensions + event.createCommand('list', { + description: 'list available extensions', + handler: async (_, console) => { + + // Get extnsion information from context + const extensionInfos = Context.get('extensionInfo'); + + // Iterate over extension infos + for ( const info of Object.values(extensionInfos) ) { + + // Construct a string + const moduleType = info.type === 'module' + ? '\x1B[32;1m(ESM)\x1B[0m' + : '\x1B[33;1m(CJS)\x1B[0m'; + let str = `- ${info.name} ${moduleType}`; + if ( info.priority !== 0 ) { + str += ` (priority ${info.priority})`; + } + + // Print a string + console.log(str); + } + }, + }); +}); diff --git a/src/backend/src/ExtensionService.js b/src/backend/src/ExtensionService.js index b6446dab6..626d11482 100644 --- a/src/backend/src/ExtensionService.js +++ b/src/backend/src/ExtensionService.js @@ -95,19 +95,19 @@ class ExtensionService extends BaseService { svc_event.on_all(async (key, data, meta = {}) => { meta.from_outside_of_extension = true; - const promises = []; - - // push event to the extension's event bus - promises.push( - this.state.extension.emit(key, data, meta)); - - // legacy: older extensions prefix "core." to events from Puter - promises.push( - this.state.extension.emit(`core.${key}`, data, meta)); - - // future: going to remove 'boot.' prefix from lifecycle events - - await Promise.all(promises); + await Context.sub({ + extension_name: this.state.extension.name, + }).arun(async () => { + const promises = [ + // push event to the extension's event bus + this.state.extension.emit(key, data, meta), + // legacy: older extensions prefix "core." to events from Puter + this.state.extension.emit(`core.${key}`, data, meta), + ]; + // await this.state.extension.emit(key, data, meta); + await Promise.all(promises); + }); + // await Promise.all(promises); }); // Propagate all events from extension to Puter's event bus diff --git a/src/backend/src/Kernel.js b/src/backend/src/Kernel.js index ee9436f73..bbf3313eb 100644 --- a/src/backend/src/Kernel.js +++ b/src/backend/src/Kernel.js @@ -51,6 +51,7 @@ class Kernel extends AdvancedBase { this.entry_path = entry_path; this.extensionExports = {}; + this.extensionInfo = {}; this.registry = {}; this.runtimeModuleRegistry = new RuntimeModuleRegistry(); @@ -126,6 +127,7 @@ class Kernel extends AdvancedBase { config, logger: this.bootLogger, extensionExports: this.extensionExports, + extensionInfo: this.extensionInfo, registry: this.registry, args, ['runtime-modules']: this.runtimeModuleRegistry, @@ -447,6 +449,11 @@ class Kernel extends AdvancedBase { const extension_name = exportObject?.name ?? packageJSON.name; this.extensionExports[extension_name] = exportObject; + this.extensionInfo[extension_name] = { + name: extension_name, + priority: mod_entry.priority, + type: packageJSON?.type ?? 'commonjs', + }; mod.extension.registry = this.registry; mod.extension.name = extension_name; diff --git a/src/backend/src/services/CommandService.js b/src/backend/src/services/CommandService.js index 983f8e69c..e1c5d5ce0 100644 --- a/src/backend/src/services/CommandService.js +++ b/src/backend/src/services/CommandService.js @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +const { Context } = require("../util/context"); const BaseService = require("./BaseService"); @@ -100,6 +101,30 @@ class CommandService extends BaseService { } })); } + + async ['__on_boot.consolidation'] () { + const svc_event = this.services.get('event'); + const svc_command = this; + const event = { + createCommand (name, command) { + const serviceName = Context.get('extension_name') ?? '%missing%'; + const commandSpec = typeof command === 'function' + ? { handler: command } + : command; + if ( typeof commandSpec !== 'object' ) { + throw new Error('command must be either a function or an object'); + } + if ( ! (typeof command.handler === 'function') ) { + throw new Error('command should have a handler function'); + } + svc_command.registerCommands(serviceName, [{ + id: name, + ...commandSpec, + }]); + }, + }; + svc_event.emit('create.commands', event); + } registerCommands(serviceName, commands) { if ( ! this.log ) process.exit(1);