Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions packages/cli/src/__tests__/runtime-host-operator-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
type RuntimeHostAccessIssueOptions,
} from '../runtime-host-access-command.js';
import { parseRuntimeHostCommand } from '../runtime-host-cli.js';
import { runRuntimeHostPluginCli } from '../runtime-host-plugin-command.js';
import { runRuntimeHostProjectCli } from '../runtime-host-project-command.js';
import { createRuntimeHostServiceReadyEvent } from '../runtime-host-service-command.js';

Expand All @@ -53,6 +54,24 @@ describe('Runtime Host operator commands', () => {
prefer: false,
},
);
assert.deepEqual(
parseRuntimeHostCommand(['plugin', 'inspect', '--scope', 'profile', '--limit', '16']),
{
kind: 'runtime-host-plugin',
action: 'inspect',
rootId: 'profile',
limit: 16,
},
);
assert.deepEqual(
parseRuntimeHostCommand(['plugin', 'export', 'fixture-plugin', './fixture.maka-extension']),
{
kind: 'runtime-host-plugin',
action: 'export',
subject: 'fixture-plugin',
targetPath: './fixture.maka-extension',
},
);
assert.deepEqual(
parseRuntimeHostCommand([
'project',
Expand Down Expand Up @@ -318,6 +337,59 @@ describe('Runtime Host operator commands', () => {
['project-1', 'project-1'],
);
});

test('uses every Plugin Platform surface through the Runtime Host', async () => {
const requests: unknown[] = [];
let closeCount = 0;
const connection = {
request: async (operation: string, input: unknown) => {
requests.push({ operation, input });
return {};
},
close: async () => {
closeCount += 1;
},
} as unknown as RuntimeHostConnection;
const overrides = {
connect: async () => connection,
readText: async () => '{"operations":[{"type":"remove","entryId":"entry-one"}]}',
write: () => undefined,
};
const commands = [
{ rootPath: '/srv/maka', action: 'status' as const },
{ rootPath: '/srv/maka', action: 'list' as const },
{ rootPath: '/srv/maka', action: 'inspect' as const, rootId: 'profile' },
{ rootPath: '/srv/maka', action: 'failures' as const },
{ rootPath: '/srv/maka', action: 'install' as const, subject: './plugin' },
{ rootPath: '/srv/maka', action: 'uninstall' as const, subject: 'plugin' },
{ rootPath: '/srv/maka', action: 'reload' as const, subject: 'plugin' },
{
rootPath: '/srv/maka',
action: 'export' as const,
subject: 'plugin',
targetPath: './plugin.maka-extension',
},
{ rootPath: '/srv/maka', action: 'apply' as const, subject: './operations.json' },
];
for (const command of commands) {
assert.equal(await runRuntimeHostPluginCli(command, overrides), 0);
}
assert.deepEqual(
requests.map((request) => (request as { operation: string }).operation),
[
'plugin.platform.query',
'plugin.platform.query',
'plugin.platform.query',
'plugin.platform.query',
'plugin.package.install',
'plugin.package.uninstall',
'plugin.package.reload',
'plugin.package.export',
'plugin.composition.apply',
],
);
assert.equal(closeCount, commands.length);
});
});

function presetOptions(
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/cli-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ function helpText(cliCommand: string): string {
` ${cliCommand} runtime-host access revoke --credential <id>`,
` ${cliCommand} runtime-host project list [--root <path>]`,
` ${cliCommand} runtime-host project add <path> [--prefer] [--root <path>]`,
` ${cliCommand} runtime-host plugin status|list|inspect|failures [--root <path>]`,
` ${cliCommand} runtime-host plugin install|uninstall|reload <target> [--root <path>]`,
` ${cliCommand} runtime-host plugin export <extension-id> <bundle-path> [--root <path>]`,
` ${cliCommand} runtime-host plugin apply <operations.json> [--root <path>]`,
` ${cliCommand} runtime-host profile list`,
` ${cliCommand} runtime-host profile set --id <id> --name <name> --tls-url <wss-url> --expected-root <root-id> [--credential-env <name>]`,
` ${cliCommand} runtime-host profile set --id <id> --name <name> --ssh-destination <user@host> --ssh-remote-port <port> --expected-root <root-id> [--ssh-port <port>] [--credential-env <name>]`,
Expand Down Expand Up @@ -419,6 +423,18 @@ export async function runMakaCli(
prefer: command.prefer,
});
}
case 'runtime-host-plugin': {
const { runRuntimeHostPluginCli } = await import('./runtime-host-plugin-command.js');
return runRuntimeHostPluginCli({
rootPath: command.rootPath ?? dataRoots.workspaceRoot,
action: command.action,
...(command.subject ? { subject: command.subject } : {}),
...(command.targetPath ? { targetPath: command.targetPath } : {}),
...(command.rootId ? { rootId: command.rootId } : {}),
...(command.cursor === undefined ? {} : { cursor: command.cursor }),
...(command.limit === undefined ? {} : { limit: command.limit }),
});
}
case 'runtime-host-capability-provider-serve': {
const { runRuntimeHostCapabilityProviderCli } = await import(
'./runtime-host-capability-provider-command.js'
Expand Down
107 changes: 106 additions & 1 deletion packages/cli/src/runtime-host-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ export type RuntimeHostCliCommand =
}
| { kind: 'runtime-host-project-list'; rootPath?: string }
| { kind: 'runtime-host-project-add'; rootPath?: string; path: string; prefer: boolean }
| {
kind: 'runtime-host-plugin';
rootPath?: string;
action:
| 'status'
| 'list'
| 'inspect'
| 'failures'
| 'install'
| 'uninstall'
| 'reload'
| 'export'
| 'apply';
subject?: string;
targetPath?: string;
rootId?: string;
cursor?: number;
limit?: number;
}
| {
kind: 'runtime-host-capability-provider-serve';
url: string;
Expand Down Expand Up @@ -184,14 +203,15 @@ export function parseRuntimeHostCommand(argv: string[]): RuntimeHostCliCommand {
if (argv[0] === 'service') return parseServiceManagementCommand(argv.slice(1));
if (argv[0] === 'access') return parseAccessCommand(argv.slice(1));
if (argv[0] === 'project') return parseProjectCommand(argv.slice(1));
if (argv[0] === 'plugin') return parsePluginCommand(argv.slice(1));
if (argv[0] === 'capability-provider') {
return parseCapabilityProviderCommand(argv.slice(1));
}
if (argv[0] === 'profile') return parseProfileCommand(argv.slice(1));
return error(
argv[0]
? `Unexpected runtime-host command: ${argv[0]}`
: 'runtime-host requires the serve, setup, service, access, project, profile, or capability-provider command',
: 'runtime-host requires the serve, setup, service, access, project, plugin, profile, or capability-provider command',
);
}

Expand Down Expand Up @@ -599,6 +619,91 @@ function parseProjectCommand(argv: string[]): RuntimeHostCliCommand {
};
}

function parsePluginCommand(argv: string[]): RuntimeHostCliCommand {
const action = argv[0];
const actions = [
'status',
'list',
'inspect',
'failures',
'install',
'uninstall',
'reload',
'export',
'apply',
] as const;
if (!actions.includes(action as (typeof actions)[number])) {
return error(
action
? `Unexpected runtime-host plugin command: ${action}`
: 'runtime-host plugin requires an action',
);
}
let rootPath: string | undefined;
let rootId: string | undefined;
let cursor: number | undefined;
let limit: number | undefined;
const positional: string[] = [];
for (let index = 1; index < argv.length; index += 1) {
const argument = argv[index];
if (
argument === '--root' ||
argument === '--scope' ||
argument === '--cursor' ||
argument === '--limit'
) {
const parsed = optionValue(argv, index, argument);
if (typeof parsed !== 'string') return parsed;
if (argument === '--root') rootPath = parsed;
else if (argument === '--scope') rootId = parsed;
else {
const numeric = Number(parsed);
if (
!Number.isSafeInteger(numeric) ||
numeric < 0 ||
(argument === '--limit' && (numeric < 1 || numeric > 64))
) {
return error(`${argument} requires a non-negative integer`);
}
if (argument === '--cursor') cursor = numeric;
else limit = numeric;
}
index += 1;
continue;
}
positional.push(argument ?? '');
}
const selected = action as (typeof actions)[number];
const expected =
selected === 'export'
? 2
: ['install', 'uninstall', 'reload', 'apply'].includes(selected)
? 1
: 0;
if (positional.length !== expected) {
return error(
`runtime-host plugin ${selected} requires ${expected} target${expected === 1 ? '' : 's'}`,
);
}
if (rootId && selected !== 'inspect') return error('--scope is only valid for plugin inspect');
if (
(cursor !== undefined || limit !== undefined) &&
!['list', 'inspect', 'failures'].includes(selected)
) {
return error('--cursor and --limit require a paged Plugin query');
}
return {
kind: 'runtime-host-plugin',
action: selected,
...(rootPath ? { rootPath } : {}),
...(positional[0] ? { subject: positional[0] } : {}),
...(positional[1] ? { targetPath: positional[1] } : {}),
...(rootId ? { rootId } : {}),
...(cursor === undefined ? {} : { cursor }),
...(limit === undefined ? {} : { limit }),
};
}

function parseProfileCommand(argv: string[]): RuntimeHostCliCommand {
const action = argv[0];
if (action === 'list') {
Expand Down
Loading