dev: add command to list extensions

This commit adds the `extension:list` command for listing extensions.
This command is itself defined in an extension. To make this possible,
an event called 'create.commands' is emitted form CommandService with
the event object holding a function that can be used to add commands.
This commit is contained in:
KernelDeimos
2025-09-26 15:27:59 -04:00
parent 7b4c62d1c3
commit cb31c1d44e
4 changed files with 77 additions and 13 deletions
+32
View File
@@ -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);
}
},
});
});
+13 -13
View File
@@ -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
+7
View File
@@ -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;
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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);