@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
96 lines (95 loc) • 4.2 kB
JavaScript
/*
* Copyright 2025 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Runs a shell command and returns an object that mimics the interface of a ChildProcess object returned by Node's spawn function.
*
* This function is intended to be used only when Headlamp is in app mode.
*
* @see handleRunCommand in app/electron/main.ts
*
* This function uses the desktopApi.send and desktopApi.receive methods to communicate with the main process.
* @param command - The command to run.
* @param args - An array of arguments to pass to the command.
* @param options - Additional options for the command.
* @param permissionSecrets - Internal use. A record of permission secrets that may be required for the command.
* @param desktopApiSend - Internal use. The function to send data to the main process.
* @param desktopApiReceive - Internal use. The function to receive data from the main process.
* @returns An object with `stdout`, `stderr`, and `on` properties. You can listen for 'data' events on `stdout` and `stderr`, and 'exit' events with `on`.
* @example
*
* How it can be used in a plugin:
* ```ts
* declare const pluginRunCommand: typeof runCommand;
* const minikube = pluginRunCommand('minikube', ['status'], {});
*
* minikube.stdout.on('data', (data) => {
* console.log('stdout:', data);
* });
* minikube.stderr.on('data', (data) => {
* console.log('stderr:', data);
* });
* minikube.on('exit', (code) => {
* console.log('exit code:', code);
* });
* ```
*/
export function runCommand(command, args, options, permissionSecrets, desktopApiSend, desktopApiReceive) {
if (!window.desktopApi) {
throw new Error('runCommand only works in Headlamp app mode.');
}
if (!desktopApiSend || !desktopApiReceive || !permissionSecrets) {
// these are only optional for the pluginRunCommand
throw new Error('Do not use runCommand directly. Use pluginRunCommand via:' +
' `declare const pluginRunCommand: typeof runCommand;`');
}
// Generate a unique ID for the command, so that we can distinguish between
// multiple commands running at the same time.
const id = `${new Date().getTime()}-${Math.random().toString(36)}`;
const stdout = new EventTarget();
desktopApiReceive('command-stdout', (cmdId, data) => {
if (cmdId === id) {
const event = new CustomEvent('data', { detail: data });
stdout.dispatchEvent(event);
}
});
const stderr = new EventTarget();
desktopApiReceive('command-stderr', (cmdId, data) => {
if (cmdId === id) {
const event = new CustomEvent('data', { detail: data });
stderr.dispatchEvent(event);
}
});
const exit = new EventTarget();
desktopApiReceive('command-exit', (cmdId, code) => {
if (cmdId === id) {
const event = new CustomEvent('exit', { detail: code });
exit.dispatchEvent(event);
}
});
// We use desktopApiReceive and desktopApiSend to communicate with the main process.
// Because other plugins may change the global window.desktopApi functions
// to snoop on the secrets that plugins are sending.
desktopApiSend('run-command', { id, command, args, options, permissionSecrets });
return {
stdout: {
on: (event, listener) => stdout.addEventListener(event, (e) => listener(e.detail)),
},
stderr: {
on: (event, listener) => stderr.addEventListener(event, (e) => listener(e.detail)),
},
on: (event, listener) => exit.addEventListener(event, (e) => listener(e.detail)),
};
}