update
This commit is contained in:
+11
-1
@@ -7,6 +7,7 @@ import { jobWithTail, listJobs, readJob, runJob } from "./src/jobs";
|
||||
import { runChecks } from "./src/check";
|
||||
import { runSsh } from "./src/ssh";
|
||||
import { extractRemoteCliOptions, runRemoteCli } from "./src/remote";
|
||||
import { runMicroserviceCommand } from "./src/microservices";
|
||||
|
||||
const remoteOptions = extractRemoteCliOptions(process.argv.slice(2));
|
||||
const args = remoteOptions.args;
|
||||
@@ -27,10 +28,14 @@ function help(): unknown {
|
||||
{ command: "server logs [--tail-bytes N]", description: "Return bounded tails from file logs and docker logs." },
|
||||
{ command: "server rebuild <backend-core|frontend|provider-gateway>", description: "Build first, then label-replace one service without Docker Compose v1 recreate fallback." },
|
||||
{ command: "ssh <providerId> [ssh-like args...]", description: "Open a Host SSH / WSL SSH maintenance session through the provider-gateway bridge." },
|
||||
{ command: "microservice list", description: "List UniDesk-managed microservices and their provider/runtime mapping." },
|
||||
{ command: "microservice status <id>", description: "Show one microservice config, repository reference, backend mapping, and runtime status." },
|
||||
{ command: "microservice health <id>", description: "Probe one microservice through backend-core -> provider-gateway HTTP proxy." },
|
||||
{ command: "microservice proxy <id> <path>", description: "GET a private microservice backend path through the same frontend-only proxy used by WebUI." },
|
||||
{ command: "job list", description: "List async jobs from .state/jobs." },
|
||||
{ command: "job status <jobId|latest> [--tail-bytes N]", description: "Show job state with bounded stdout/stderr tails." },
|
||||
{ command: "debug health", description: "Probe internal core, nodes, system/Docker status, frontend, provider ingress, and public boundary." },
|
||||
{ command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." },
|
||||
{ command: "debug dispatch [providerId] [docker.ps|provider.upgrade|host.ssh|microservice.http|echo] [--wait-ms N]", description: "Submit a real internal-core dispatch request for CLI debugging." },
|
||||
{ command: "debug task <taskId|latest>", description: "Read a dispatched task record from internal core for CLI debugging." },
|
||||
{ command: "e2e run", description: "Run public frontend/provider, internal core/database, and Playwright login E2E checks." },
|
||||
],
|
||||
@@ -151,6 +156,11 @@ async function main(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (top === "microservice") {
|
||||
emitJson(commandName, await runMicroserviceCommand(config, args.slice(1)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (top === "job") {
|
||||
if (sub === "list") {
|
||||
emitJson(commandName, { jobs: listJobs() });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { runCommand } from "./command";
|
||||
import { type UniDeskConfig, repoRoot } from "./config";
|
||||
|
||||
export const dispatchCommands = ["docker.ps", "provider.upgrade", "host.ssh", "echo"] as const;
|
||||
export const dispatchCommands = ["docker.ps", "provider.upgrade", "host.ssh", "microservice.http", "echo"] as const;
|
||||
export type DebugDispatchCommand = typeof dispatchCommands[number];
|
||||
|
||||
export function isDebugDispatchCommand(value: unknown): value is DebugDispatchCommand {
|
||||
|
||||
+27
-1
@@ -354,6 +354,28 @@ async function remoteDebugTask(session: FrontendSession, args: string[]): Promis
|
||||
return { transport: "frontend", tasksResponse, taskId, task: task ?? null };
|
||||
}
|
||||
|
||||
async function remoteMicroservice(session: FrontendSession, args: string[]): Promise<unknown> {
|
||||
const action = args[1] ?? "list";
|
||||
const id = args[2];
|
||||
const path = args[3];
|
||||
if (action === "list") {
|
||||
return { transport: "frontend", response: await frontendJson(session, "/api/microservices", undefined, 12_000) };
|
||||
}
|
||||
if ((action === "status" || action === "health") && id !== undefined) {
|
||||
return {
|
||||
transport: "frontend",
|
||||
response: await frontendJson(session, `/api/microservices/${encodeURIComponent(id)}/${action}`, undefined, 18_000),
|
||||
};
|
||||
}
|
||||
if (action === "proxy" && id !== undefined && path !== undefined && path.startsWith("/")) {
|
||||
return {
|
||||
transport: "frontend",
|
||||
response: await frontendJson(session, `/api/microservices/${encodeURIComponent(id)}/proxy${path}`, undefined, 24_000),
|
||||
};
|
||||
}
|
||||
throw new Error("remote microservice command must be: microservice list | status <id> | health <id> | proxy <id> <path>");
|
||||
}
|
||||
|
||||
async function runRemoteSshOverFrontend(session: FrontendSession, providerId: string | undefined, args: string[]): Promise<number> {
|
||||
if (!providerId) throw new Error("remote ssh requires provider id, for example: bun scripts/cli.ts --main-server-ip 74.48.78.17 ssh D601 hostname");
|
||||
const parsed = parseSshArgs(args);
|
||||
@@ -399,7 +421,7 @@ async function runRemoteCliOverFrontend(options: RemoteCliOptions, config: UniDe
|
||||
emitRemoteJson(name, {
|
||||
transport: "frontend",
|
||||
baseUrl: session.baseUrl,
|
||||
commands: ["debug health", "debug dispatch", "debug task", "ssh <providerId> <command>"],
|
||||
commands: ["debug health", "debug dispatch", "debug task", "ssh <providerId> <command>", "microservice list", "microservice status <id>", "microservice health <id>", "microservice proxy <id> <path>"],
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
@@ -415,6 +437,10 @@ async function runRemoteCliOverFrontend(options: RemoteCliOptions, config: UniDe
|
||||
emitRemoteJson(name, await remoteDebugTask(session, args));
|
||||
return 0;
|
||||
}
|
||||
if (top === "microservice") {
|
||||
emitRemoteJson(name, await remoteMicroservice(session, args));
|
||||
return 0;
|
||||
}
|
||||
if (top === "ssh") {
|
||||
return await runRemoteSshOverFrontend(session, sub, args.slice(2));
|
||||
}
|
||||
|
||||
@@ -744,11 +744,14 @@ async function dispatchTask(req: Request): Promise<Response> {
|
||||
return jsonResponse({ ok: false, error: "providerId is required" }, 400);
|
||||
}
|
||||
if (command === null) {
|
||||
return jsonResponse({ ok: false, error: "command must be one of docker.ps, provider.upgrade, host.ssh, echo" }, 400);
|
||||
return jsonResponse({ ok: false, error: "command must be one of docker.ps, provider.upgrade, host.ssh, microservice.http, echo" }, 400);
|
||||
}
|
||||
if (command === "host.ssh" && !(await providerSupports(providerId, "host.ssh"))) {
|
||||
return jsonResponse({ ok: false, error: `provider does not declare host.ssh capability: ${providerId}` }, 409);
|
||||
}
|
||||
if (command === "microservice.http" && !(await providerSupports(providerId, "microservice.http"))) {
|
||||
return jsonResponse({ ok: false, error: `provider does not declare microservice.http capability: ${providerId}` }, 409);
|
||||
}
|
||||
const taskId = `task_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
||||
await sql`
|
||||
INSERT INTO unidesk_tasks (id, provider_id, command, status, payload, result)
|
||||
|
||||
@@ -193,7 +193,7 @@ function sendJson(value: unknown): void {
|
||||
}
|
||||
|
||||
function sendRegister(): void {
|
||||
const capabilities = ["heartbeat", "system.status", "docker.status", "docker.ps", "provider.upgrade", "echo"];
|
||||
const capabilities = ["heartbeat", "system.status", "docker.status", "docker.ps", "provider.upgrade", "microservice.http", "echo"];
|
||||
if (isHostSshConfigured()) capabilities.push("host.ssh");
|
||||
sendJson({
|
||||
type: "register",
|
||||
@@ -609,6 +609,45 @@ function defaultHostSshProbeCommand(): string {
|
||||
return "printf 'UNIDESK_SSH_TEST user=%s host=%s bridge=%s cwd=%s\\n' \"$(whoami)\" \"$(hostname)\" \"${UNIDESK_BRIDGE:-}\" \"$(pwd)\"";
|
||||
}
|
||||
|
||||
function normalizeShellCommand(command: string): string {
|
||||
return command.replace(/\\\r?\n/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
||||
}
|
||||
|
||||
function hostSshSelfMutationReason(command: string, cwd: string | null): string | null {
|
||||
const normalized = normalizeShellCommand(command);
|
||||
const currentContainerName = `unidesk-provider-gateway-${safeDockerName(config.providerId)}`.toLowerCase();
|
||||
const composeProject = config.upgradeComposeProject.toLowerCase();
|
||||
const composeService = config.upgradeService.toLowerCase();
|
||||
const composeFile = config.upgradeComposeFile.toLowerCase();
|
||||
const composeEnvFile = config.upgradeEnvFile.toLowerCase();
|
||||
const hostProjectRoot = config.upgradeHostProjectRoot.toLowerCase();
|
||||
const currentCwd = (cwd ?? "").toLowerCase();
|
||||
const mutatesCompose = /\bdocker\s+compose\b|\bdocker-compose\b/.test(normalized)
|
||||
&& /\b(up|build|restart|stop|rm|down|kill)\b/.test(normalized);
|
||||
const mentionsCurrentProvider = normalized.includes(currentContainerName)
|
||||
|| normalized.includes(composeProject)
|
||||
|| normalized.includes(composeService)
|
||||
|| normalized.includes(composeFile)
|
||||
|| normalized.includes(composeEnvFile)
|
||||
|| normalized.includes(hostProjectRoot)
|
||||
|| (currentCwd.length > 0 && currentCwd.startsWith(hostProjectRoot));
|
||||
if (mutatesCompose && mentionsCurrentProvider) {
|
||||
return "docker compose mutation targets the current provider-gateway deployment";
|
||||
}
|
||||
const mutatesContainer = /\bdocker\s+(container\s+)?(rm|stop|restart|kill)\b/.test(normalized);
|
||||
if (mutatesContainer && normalized.includes(currentContainerName)) {
|
||||
return "docker container mutation targets the current provider-gateway container";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function assertHostSshCommandAllowed(command: string, cwd: string | null): void {
|
||||
const reason = hostSshSelfMutationReason(command, cwd);
|
||||
if (reason !== null) {
|
||||
throw new Error(`blocked unsafe host.ssh self-mutation: ${reason}; use provider.upgrade mode=schedule or a detached local node shell instead`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runHostSsh(payload: Record<string, JsonValue>): Promise<JsonValue> {
|
||||
if (!isHostSshConfigured()) {
|
||||
throw new Error(`host SSH bridge is not configured; missing ${missingHostSshFields().join(", ")}`);
|
||||
@@ -629,6 +668,7 @@ async function runHostSsh(payload: Record<string, JsonValue>): Promise<JsonValue
|
||||
throw new Error(`host SSH command is too long: ${command.length} bytes`);
|
||||
}
|
||||
const cwd = payloadString(payload, "cwd") ?? config.hostRemoteCwd;
|
||||
if (mode === "exec") assertHostSshCommandAllowed(command, cwd);
|
||||
const scriptParts = [
|
||||
"set -e",
|
||||
cwd === null ? null : `cd ${shellQuote(cwd)}`,
|
||||
@@ -682,6 +722,7 @@ async function runHostSsh(payload: Record<string, JsonValue>): Promise<JsonValue
|
||||
function hostSshRemoteScript(command: string | null, cwd: string | null, cols?: number, rows?: number): string {
|
||||
const fallbackCwd = config.hostRemoteCwd ?? `/home/${config.hostSshUser ?? "root"}`;
|
||||
const requestedCwd = cwd ?? fallbackCwd;
|
||||
if (command !== null && command.length > 0) assertHostSshCommandAllowed(command, requestedCwd);
|
||||
const loginShell = config.hostLoginShell ?? "/bin/bash";
|
||||
const resize = Number.isFinite(cols) && Number.isFinite(rows)
|
||||
? `stty rows ${Math.max(8, Math.min(120, Math.floor(rows ?? 30)))} cols ${Math.max(20, Math.min(300, Math.floor(cols ?? 100)))} 2>/dev/null || true`
|
||||
@@ -846,7 +887,7 @@ function safeDockerName(value: string): string {
|
||||
|
||||
function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
const workspace = config.upgradeWorkspacePath;
|
||||
const composeCommand = [
|
||||
const composeBaseCommand = [
|
||||
"docker",
|
||||
"compose",
|
||||
"--env-file",
|
||||
@@ -855,14 +896,39 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
`${workspace}/${config.upgradeComposeFile}`,
|
||||
"-p",
|
||||
config.upgradeComposeProject,
|
||||
];
|
||||
const composeBuildCommand = [
|
||||
...composeBaseCommand,
|
||||
"build",
|
||||
config.upgradeService,
|
||||
];
|
||||
const listServiceContainersCommand = [
|
||||
"docker",
|
||||
"ps",
|
||||
"-aq",
|
||||
"--filter",
|
||||
`label=com.docker.compose.project=${config.upgradeComposeProject}`,
|
||||
"--filter",
|
||||
`label=com.docker.compose.service=${config.upgradeService}`,
|
||||
];
|
||||
const composeUpCommand = [
|
||||
...composeBaseCommand,
|
||||
"up",
|
||||
"-d",
|
||||
"--no-deps",
|
||||
"--build",
|
||||
"--force-recreate",
|
||||
config.upgradeService,
|
||||
];
|
||||
const updaterName = `unidesk-provider-upgrader-${safeDockerName(taskId)}`;
|
||||
const script = `set -eu; sleep 2; cd ${shellQuote(workspace)}; ${composeCommand.map(shellQuote).join(" ")}`;
|
||||
const script = [
|
||||
"set -eu",
|
||||
"sleep 2",
|
||||
`cd ${shellQuote(workspace)}`,
|
||||
composeBuildCommand.map(shellQuote).join(" "),
|
||||
`ids=$(${listServiceContainersCommand.map(shellQuote).join(" ")})`,
|
||||
`if [ -n "$ids" ]; then docker rm -f $ids; fi`,
|
||||
composeUpCommand.map(shellQuote).join(" "),
|
||||
].join("; ");
|
||||
const dockerRunCommand = [
|
||||
"docker",
|
||||
"run",
|
||||
@@ -894,7 +960,19 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
composeFile: config.upgradeComposeFile,
|
||||
envFile: config.upgradeEnvFile,
|
||||
},
|
||||
composeCommand,
|
||||
composeCommand: composeUpCommand,
|
||||
composeBuildCommand,
|
||||
listServiceContainersCommand,
|
||||
composeUpCommand,
|
||||
replacementStrategy: {
|
||||
buildBeforeRemove: true,
|
||||
removeScope: {
|
||||
projectLabel: config.upgradeComposeProject,
|
||||
serviceLabel: config.upgradeService,
|
||||
},
|
||||
noDeps: true,
|
||||
namedVolumesPreserved: true,
|
||||
},
|
||||
dockerRunCommand,
|
||||
};
|
||||
}
|
||||
@@ -917,6 +995,132 @@ async function runProviderUpgrade(taskId: string, payload: Record<string, JsonVa
|
||||
};
|
||||
}
|
||||
|
||||
function payloadNumber(payload: Record<string, JsonValue>, key: string, fallback: number): number {
|
||||
const raw = payload[key];
|
||||
const value = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw) : fallback;
|
||||
if (!Number.isFinite(value) || value <= 0) return fallback;
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function payloadJsonArrayLimits(payload: Record<string, JsonValue>): Record<string, number> {
|
||||
const raw = payload.jsonArrayLimits;
|
||||
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
||||
const limits: Record<string, number> = {};
|
||||
for (const [path, value] of Object.entries(raw)) {
|
||||
if (!/^[A-Za-z0-9_.-]+$/.test(path)) continue;
|
||||
const limit = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
||||
if (Number.isInteger(limit) && limit > 0 && limit <= 500) limits[path] = limit;
|
||||
}
|
||||
return limits;
|
||||
}
|
||||
|
||||
function assertAllowedMicroserviceBase(rawBaseUrl: string): URL {
|
||||
const baseUrl = new URL(rawBaseUrl);
|
||||
if (baseUrl.protocol !== "http:") throw new Error(`microservice backend only supports http URLs, got ${baseUrl.protocol}`);
|
||||
const host = baseUrl.hostname.toLowerCase();
|
||||
const allowedHosts = new Set(["127.0.0.1", "localhost", "host.docker.internal"]);
|
||||
if (!allowedHosts.has(host)) throw new Error(`microservice backend host is not allowed: ${baseUrl.hostname}`);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
function arrayAtPath(value: unknown, path: string): JsonValue[] | null {
|
||||
let current: unknown = value;
|
||||
for (const part of path.split(".")) {
|
||||
if (typeof current !== "object" || current === null || Array.isArray(current)) return null;
|
||||
current = (current as Record<string, unknown>)[part];
|
||||
}
|
||||
return Array.isArray(current) ? current as JsonValue[] : null;
|
||||
}
|
||||
|
||||
function applyJsonArrayLimits(bodyText: string, contentType: string, limits: Record<string, number>): { bodyText: string; transform: JsonValue } {
|
||||
const entries = Object.entries(limits);
|
||||
if (entries.length === 0) return { bodyText, transform: { applied: false } };
|
||||
if (!contentType.toLowerCase().includes("json")) {
|
||||
return { bodyText, transform: { applied: false, reason: "content-type is not json" } };
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(bodyText) as unknown;
|
||||
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
||||
return { bodyText, transform: { applied: false, reason: "json root is not an object" } };
|
||||
}
|
||||
const root = parsed as Record<string, unknown>;
|
||||
const applied: Record<string, JsonValue> = {};
|
||||
for (const [path, limit] of entries) {
|
||||
const array = arrayAtPath(root, path);
|
||||
if (array === null) continue;
|
||||
const originalLength = array.length;
|
||||
if (array.length > limit) array.splice(limit);
|
||||
applied[path] = { limit, originalLength, returnedLength: array.length };
|
||||
}
|
||||
root._unidesk = { arrayLimits: applied };
|
||||
return { bodyText: JSON.stringify(parsed), transform: { applied: Object.keys(applied).length > 0, arrayLimits: applied } };
|
||||
} catch (error) {
|
||||
return { bodyText, transform: { applied: false, error: error instanceof Error ? error.message : String(error) } };
|
||||
}
|
||||
}
|
||||
|
||||
async function runMicroserviceHttp(payload: Record<string, JsonValue>): Promise<JsonValue> {
|
||||
const rawMethod = String(payload.method || "GET").toUpperCase();
|
||||
const allowedMethods = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"]);
|
||||
const method = allowedMethods.has(rawMethod) ? rawMethod : "GET";
|
||||
const targetBaseUrl = payloadString(payload, "targetBaseUrl");
|
||||
if (targetBaseUrl === null) throw new Error("microservice.http requires targetBaseUrl");
|
||||
const path = payloadString(payload, "path") ?? "/";
|
||||
const query = payloadString(payload, "query") ?? "";
|
||||
if (!path.startsWith("/")) throw new Error("microservice.http path must start with /");
|
||||
if (query.length > 0 && !query.startsWith("?")) throw new Error("microservice.http query must start with ?");
|
||||
const baseUrl = assertAllowedMicroserviceBase(targetBaseUrl);
|
||||
const url = new URL(path, baseUrl);
|
||||
url.search = query;
|
||||
const timeoutMs = Math.max(1000, Math.min(30_000, payloadNumber(payload, "timeoutMs", 10_000)));
|
||||
const jsonArrayLimits = payloadJsonArrayLimits(payload);
|
||||
const bodyText = payloadString(payload, "bodyText") ?? "";
|
||||
const requestHeaders = typeof payload.requestHeaders === "object" && payload.requestHeaders !== null && !Array.isArray(payload.requestHeaders)
|
||||
? payload.requestHeaders as Record<string, JsonValue>
|
||||
: {};
|
||||
const headers = new Headers();
|
||||
const contentType = typeof requestHeaders["content-type"] === "string" ? requestHeaders["content-type"] : "";
|
||||
if (contentType.length > 0) headers.set("content-type", contentType);
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: method === "GET" || method === "HEAD" ? undefined : bodyText,
|
||||
signal: controller.signal,
|
||||
});
|
||||
const rawBodyText = await response.text();
|
||||
const contentType = response.headers.get("content-type") ?? "text/plain; charset=utf-8";
|
||||
const transformed = applyJsonArrayLimits(rawBodyText, contentType, jsonArrayLimits);
|
||||
return {
|
||||
ok: true,
|
||||
serviceId: payloadString(payload, "serviceId") ?? "unknown",
|
||||
method,
|
||||
url: url.toString(),
|
||||
status: response.status,
|
||||
upstreamOk: response.ok,
|
||||
contentType,
|
||||
bodyText: truncateText(transformed.bodyText, 1024 * 1024),
|
||||
upstreamBodyBytes: rawBodyText.length,
|
||||
returnedBodyBytes: Math.min(transformed.bodyText.length, 1024 * 1024),
|
||||
truncated: transformed.bodyText.length > 1024 * 1024,
|
||||
transform: transformed.transform,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
serviceId: payloadString(payload, "serviceId") ?? "unknown",
|
||||
method,
|
||||
url: url.toString(),
|
||||
timeoutMs,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDispatch(message: CoreDispatchMessage): Promise<void> {
|
||||
logger("info", "dispatch_received", { taskId: message.taskId, command: message.command, payload: message.payload });
|
||||
await sendTaskStatus(message.taskId, "accepted", "provider accepted task");
|
||||
@@ -941,6 +1145,15 @@ async function handleDispatch(message: CoreDispatchMessage): Promise<void> {
|
||||
await sendTaskStatus(message.taskId, "succeeded", "host SSH command completed", result);
|
||||
return;
|
||||
}
|
||||
if (message.command === "microservice.http") {
|
||||
const result = await runMicroserviceHttp(message.payload);
|
||||
if ((result as { ok?: unknown }).ok !== true) {
|
||||
await sendTaskStatus(message.taskId, "failed", "microservice HTTP proxy failed", result);
|
||||
return;
|
||||
}
|
||||
await sendTaskStatus(message.taskId, "succeeded", "microservice HTTP proxy completed", result);
|
||||
return;
|
||||
}
|
||||
await sendTaskStatus(message.taskId, "succeeded", "echo completed", { echo: message.payload });
|
||||
} catch (error) {
|
||||
const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||
|
||||
@@ -106,7 +106,7 @@ export interface ProviderTaskStatusMessage {
|
||||
result?: JsonValue;
|
||||
}
|
||||
|
||||
export type ProviderDispatchCommand = "docker.ps" | "provider.upgrade" | "host.ssh" | "echo";
|
||||
export type ProviderDispatchCommand = "docker.ps" | "provider.upgrade" | "host.ssh" | "microservice.http" | "echo";
|
||||
|
||||
export interface CoreDispatchMessage {
|
||||
type: "dispatch";
|
||||
@@ -271,7 +271,7 @@ export function parseJsonObject(value: string, name: string): Record<string, Jso
|
||||
}
|
||||
|
||||
export function isProviderDispatchCommand(value: unknown): value is ProviderDispatchCommand {
|
||||
return value === "docker.ps" || value === "provider.upgrade" || value === "host.ssh" || value === "echo";
|
||||
return value === "docker.ps" || value === "provider.upgrade" || value === "host.ssh" || value === "microservice.http" || value === "echo";
|
||||
}
|
||||
|
||||
export function isProviderToCoreMessage(value: unknown): value is ProviderToCoreMessage {
|
||||
|
||||
Reference in New Issue
Block a user