fix: route publish dry-run through remote control plane
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { readConfig } from "./src/config";
|
||||
import { runCiPublishUserServiceDryRunPreflight, type PublishPreflightTransport } from "./src/ci";
|
||||
import { autoRemoteCiPublishUserServiceDryRunPlan } from "./src/remote";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
@@ -46,6 +47,35 @@ const infraBlockedTransport: PublishPreflightTransport = {
|
||||
};
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const autoRemote = autoRemoteCiPublishUserServiceDryRunPlan(config, [
|
||||
"ci",
|
||||
"publish-user-service",
|
||||
"--service",
|
||||
"frontend",
|
||||
"--commit",
|
||||
commit,
|
||||
"--dry-run",
|
||||
], {
|
||||
CODE_QUEUE_SERVICE_ROLE: "scheduler",
|
||||
CODE_QUEUE_DEV_CONTAINER_MASTER_HOST: "74.48.78.17",
|
||||
});
|
||||
assertCondition(autoRemote.enabled === true, "Code Queue runner publish dry-run should auto-select remote frontend transport", autoRemote);
|
||||
assertCondition(autoRemote.host === "74.48.78.17", "auto remote plan should use CODE_QUEUE_DEV_CONTAINER_MASTER_HOST", autoRemote);
|
||||
assertCondition(autoRemote.transport === "frontend", "auto remote plan should use frontend transport", autoRemote);
|
||||
assertCondition(String(autoRemote.command ?? "").includes("--main-server-ip 74.48.78.17"), "auto remote plan should expose command shape", autoRemote);
|
||||
|
||||
const nonRunner = autoRemoteCiPublishUserServiceDryRunPlan(config, [
|
||||
"ci",
|
||||
"publish-user-service",
|
||||
"--service",
|
||||
"frontend",
|
||||
"--commit",
|
||||
commit,
|
||||
"--dry-run",
|
||||
], {});
|
||||
assertCondition(nonRunner.enabled === false, "non-runner local CLI should not auto-remote without a host hint", nonRunner);
|
||||
assertCondition(nonRunner.failureClassification === "local-docker-required", "non-runner disabled plan should classify local docker requirement", nonRunner);
|
||||
|
||||
const result = await runCiPublishUserServiceDryRunPreflight(config, [
|
||||
"publish-user-service",
|
||||
"--service",
|
||||
@@ -61,6 +91,7 @@ async function main(): Promise<void> {
|
||||
const controlChannels = Array.isArray(record.controlChannels) ? record.controlChannels.map((item) => asRecord(item, "control channel")) : [];
|
||||
const registry = asRecord(record.registry, "registry");
|
||||
const controlledPublish = asRecord(record.controlledPublish, "controlledPublish");
|
||||
const controlPlane = asRecord(record.controlPlane, "controlPlane");
|
||||
const backendCore = asRecord(channels.find((item) => item.channel === "backend-core-api")?.detail, "backendCore detail");
|
||||
const backendCoreTransport = asRecord(backendCore.detail, "backendCore transport payload");
|
||||
const backendCoreBody = asRecord(backendCoreTransport.body, "backendCore body payload");
|
||||
@@ -71,6 +102,10 @@ async function main(): Promise<void> {
|
||||
assertCondition(record.ok === false, "infra-blocked preflight should fail", record);
|
||||
assertCondition(record.mode === "dry-run-preflight", "dry-run preflight mode should be reported", record);
|
||||
assertCondition(record.runnerDisposition === "infra-blocked", "runnerDisposition should be infra-blocked", record);
|
||||
assertCondition(record.failureClassification === "local-docker-required", "local backend-core absence should classify as local-docker-required", record);
|
||||
assertCondition(controlPlane.transport === "local-docker", "local preflight should expose local-docker transport", controlPlane);
|
||||
assertCondition(controlPlane.remoteCapable === false, "local preflight should not claim remote-capable transport", controlPlane);
|
||||
assertCondition(controlPlane.preferredRunnerPath === "frontend-private-proxy", "controlPlane should name frontend private proxy as preferred path", controlPlane);
|
||||
assertCondition(Array.isArray(record.missingChannels), "missingChannels should be an array", record);
|
||||
assertCondition(Array.isArray(record.missingControlChannels), "missingControlChannels should be an array", record);
|
||||
assertCondition(missingChannels.includes("backend-core-api"), "backend-core-api should be missing", record);
|
||||
@@ -108,6 +143,8 @@ async function main(): Promise<void> {
|
||||
"artifact summary remains commit-pinned and read-only",
|
||||
"missingControlChannels maps detailed probes to backend-core/database/provider/registry",
|
||||
"controlledPublish identifies D601 unidesk-ci as the only place for the real publish",
|
||||
"runner-like environments auto-select the existing frontend remote transport",
|
||||
"local backend-core absence is classified as local-docker-required",
|
||||
],
|
||||
missingChannels: record.missingChannels,
|
||||
missingControlChannels: record.missingControlChannels,
|
||||
|
||||
+11
-1
@@ -6,7 +6,7 @@ import { emitError, emitJson } from "./src/output";
|
||||
import { jobWithTail, listJobs, listJobsSummary, readJob, runJob } from "./src/jobs";
|
||||
import { checkHelp, parseCheckOptions, runChecks } from "./src/check";
|
||||
import { runSsh } from "./src/ssh";
|
||||
import { extractRemoteCliOptions, runRemoteCli } from "./src/remote";
|
||||
import { autoRemoteCiPublishUserServiceDryRunPlan, extractRemoteCliOptions, runRemoteCli } from "./src/remote";
|
||||
import { runMicroserviceCommand } from "./src/microservices";
|
||||
import { runCodeQueueCommand } from "./src/code-queue";
|
||||
import { runDecisionCenterCommand } from "./src/decision-center";
|
||||
@@ -199,6 +199,16 @@ async function main(): Promise<void> {
|
||||
}
|
||||
|
||||
const config = readConfig();
|
||||
const autoRemoteCiPublishPlan = autoRemoteCiPublishUserServiceDryRunPlan(config, args);
|
||||
if (autoRemoteCiPublishPlan.enabled && autoRemoteCiPublishPlan.host !== null) {
|
||||
process.exitCode = await runRemoteCli({
|
||||
...remoteOptions,
|
||||
host: autoRemoteCiPublishPlan.host,
|
||||
transport: "frontend",
|
||||
args,
|
||||
}, config);
|
||||
return;
|
||||
}
|
||||
|
||||
if (top === "ssh") {
|
||||
const exitCode = await runSsh(config, sub ?? "", args.slice(2));
|
||||
|
||||
+66
-1
@@ -87,6 +87,7 @@ interface DispatchResult {
|
||||
raw: unknown;
|
||||
}
|
||||
|
||||
type PublishPreflightFailureClassification = "auth-missing" | "remote-proxy-missing" | "provider-unreachable" | "local-docker-required";
|
||||
type PublishPreflightControlChannel = "backend-core" | "database" | "provider" | "registry";
|
||||
type PublishPreflightDetailedChannel = "backend-core-api" | "provider-dispatch" | "provider-host-ssh" | "database" | "artifact-registry";
|
||||
|
||||
@@ -108,6 +109,7 @@ interface PublishPreflightControlChannelProbe {
|
||||
interface PublishPreflight {
|
||||
ok: boolean;
|
||||
runnerDisposition: "ready" | "infra-blocked";
|
||||
failureClassification: PublishPreflightFailureClassification | null;
|
||||
serviceId: string;
|
||||
commit: string;
|
||||
providerId: string;
|
||||
@@ -117,11 +119,14 @@ interface PublishPreflight {
|
||||
controlChannels: PublishPreflightControlChannelProbe[];
|
||||
channels: PublishPreflightChannelProbe[];
|
||||
registry: unknown;
|
||||
controlPlane: Record<string, unknown>;
|
||||
next: string[];
|
||||
boundary: string;
|
||||
}
|
||||
|
||||
export interface PublishPreflightTransport {
|
||||
kind?: "local-docker" | "remote-frontend" | "provider-tunnel";
|
||||
remoteHost?: string | null;
|
||||
coreFetch: (path: string, init?: { method?: string; body?: unknown; maxResponseBytes?: number }) => unknown | Promise<unknown>;
|
||||
dispatchHostSsh: (command: string, waitMs: number, remoteTimeoutMs: number) => Promise<DispatchResult>;
|
||||
commandCwd: string;
|
||||
@@ -400,13 +405,63 @@ function summarizePublishControlChannels(channels: PublishPreflightChannelProbe[
|
||||
|
||||
function backendCoreUnavailable(value: unknown): boolean {
|
||||
const record = asRecord(value);
|
||||
const body = coreBody(value);
|
||||
if (record?.runnerDisposition === "infra-blocked") return true;
|
||||
if (record?.failureKind === "target-stack-not-running") return true;
|
||||
if (body?.runnerDisposition === "infra-blocked") return true;
|
||||
if (body?.failureKind === "target-stack-not-running") return true;
|
||||
if (body?.degradedReason === "backend-core-container-missing") return true;
|
||||
const text = JSON.stringify(value) ?? "";
|
||||
return text.includes("No such container: unidesk-backend-core")
|
||||
|| text.includes("No such container: unidesk-database");
|
||||
}
|
||||
|
||||
function transportKind(transport: PublishPreflightTransport): "local-docker" | "remote-frontend" | "provider-tunnel" {
|
||||
return transport.kind ?? "local-docker";
|
||||
}
|
||||
|
||||
function classifyPublishPreflightFailure(
|
||||
transport: PublishPreflightTransport,
|
||||
overview: unknown,
|
||||
sshProbe: DispatchResult,
|
||||
registry: unknown,
|
||||
): PublishPreflightFailureClassification | null {
|
||||
const kind = transportKind(transport);
|
||||
const overviewRecord = asRecord(overview);
|
||||
if (overviewRecord?.failureClassification === "auth-missing") return "auth-missing";
|
||||
if (overviewRecord?.failureClassification === "remote-proxy-missing") return "remote-proxy-missing";
|
||||
if (overviewRecord?.failureClassification === "provider-unreachable") return "provider-unreachable";
|
||||
if (kind === "local-docker" && backendCoreUnavailable(overview)) return "local-docker-required";
|
||||
if (kind !== "local-docker" && !responseOk(overview)) return "remote-proxy-missing";
|
||||
if (!sshProbe.ok) return "provider-unreachable";
|
||||
const registryRecord = asRecord(registry);
|
||||
if (registryRecord?.ok === false) return "provider-unreachable";
|
||||
return null;
|
||||
}
|
||||
|
||||
function publishPreflightControlPlane(
|
||||
transport: PublishPreflightTransport,
|
||||
failureClassification: PublishPreflightFailureClassification | null,
|
||||
): Record<string, unknown> {
|
||||
const kind = transportKind(transport);
|
||||
const remoteCapable = kind !== "local-docker";
|
||||
return {
|
||||
transport: kind,
|
||||
remoteCapable,
|
||||
remoteHost: transport.remoteHost ?? null,
|
||||
localDockerRequired: kind === "local-docker",
|
||||
failureClassification,
|
||||
preferredRunnerPath: "frontend-private-proxy",
|
||||
fallbackRunnerPath: "main-server-local-docker",
|
||||
remoteCommandShape: "bun scripts/cli.ts --main-server-ip <host> ci publish-user-service --service <id> --commit <full-sha> --dry-run",
|
||||
providerTunnel: {
|
||||
providerId: d601ProviderId,
|
||||
command: "host.ssh",
|
||||
requiredFor: ["provider-host-ssh", "artifact-registry"],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchPreflightFailure(command: string, result: DispatchResult): DispatchResult {
|
||||
return {
|
||||
ok: false,
|
||||
@@ -468,6 +523,7 @@ function artifactRegistryProbeCommand(probe: ArtifactRegistryReadonlyProbe): str
|
||||
}
|
||||
|
||||
const localPublishPreflightTransport: PublishPreflightTransport = {
|
||||
kind: "local-docker",
|
||||
coreFetch: (path, init) => coreInternalFetch(path, init),
|
||||
dispatchHostSsh: dispatchReadonlySsh,
|
||||
commandCwd: repoRoot,
|
||||
@@ -1373,9 +1429,11 @@ async function publishUserServicePreflight(
|
||||
const controlChannels = summarizePublishControlChannels(channels);
|
||||
const missingControlChannels = controlChannels.filter((item) => !item.ok).map((item) => item.channel);
|
||||
const ready = missingChannels.length === 0;
|
||||
const failureClassification = ready ? null : classifyPublishPreflightFailure(transport, overview, sshProbe, registry);
|
||||
return {
|
||||
ok: ready,
|
||||
runnerDisposition: ready ? "ready" : "infra-blocked",
|
||||
failureClassification,
|
||||
serviceId: options.serviceId,
|
||||
commit: options.commit,
|
||||
providerId,
|
||||
@@ -1385,6 +1443,7 @@ async function publishUserServicePreflight(
|
||||
controlChannels,
|
||||
channels,
|
||||
registry,
|
||||
controlPlane: publishPreflightControlPlane(transport, failureClassification),
|
||||
next: ready
|
||||
? [
|
||||
`bun scripts/cli.ts ci publish-user-service --service ${options.serviceId} --commit ${options.commit} --wait-ms 1200000`,
|
||||
@@ -1392,7 +1451,9 @@ async function publishUserServicePreflight(
|
||||
]
|
||||
: [
|
||||
`Restore missing control channel(s): ${missingControlChannels.join(", ") || "unknown"}.`,
|
||||
"Run from the main-server CLI or use remote frontend transport against a healthy frontend/backend-core path.",
|
||||
failureClassification === "local-docker-required"
|
||||
? "From a Code Queue runner, rerun this read-only preflight through the existing remote frontend transport: bun scripts/cli.ts --main-server-ip <host> ci publish-user-service --service <id> --commit <full-sha> --dry-run."
|
||||
: "Run from the main-server CLI or use remote frontend transport against a healthy frontend/backend-core path.",
|
||||
"Restore backend-core/database/provider-gateway/Host SSH connectivity before retrying artifact publication.",
|
||||
"Use bun scripts/cli.ts artifact-registry health --provider-id D601 to recheck registry reachability after the control bridge is restored.",
|
||||
],
|
||||
@@ -1572,6 +1633,8 @@ async function publishUserServiceArtifact(config: UniDeskConfig, options: CiPubl
|
||||
missingControlChannels: preflight.missingControlChannels,
|
||||
controlChannels: preflight.controlChannels,
|
||||
channels: preflight.channels,
|
||||
failureClassification: preflight.failureClassification,
|
||||
controlPlane: preflight.controlPlane,
|
||||
registry: preflight.registry,
|
||||
sourceHostPath: options.sourceHostPath,
|
||||
source: {
|
||||
@@ -1697,6 +1760,8 @@ export async function runCiPublishUserServiceDryRunPreflight(
|
||||
missingControlChannels: preflight.missingControlChannels,
|
||||
controlChannels: preflight.controlChannels,
|
||||
channels: preflight.channels,
|
||||
failureClassification: preflight.failureClassification,
|
||||
controlPlane: preflight.controlPlane,
|
||||
registry: preflight.registry,
|
||||
sourceHostPath: options.sourceHostPath,
|
||||
source: {
|
||||
|
||||
+132
-8
@@ -23,6 +23,17 @@ export interface RemoteCliOptions {
|
||||
args: string[];
|
||||
}
|
||||
|
||||
export type RemoteFailureClassification = "auth-missing" | "remote-proxy-missing" | "provider-unreachable" | "local-docker-required";
|
||||
|
||||
export interface AutoRemoteCiPublishPlan {
|
||||
enabled: boolean;
|
||||
host: string | null;
|
||||
reason: string;
|
||||
failureClassification: RemoteFailureClassification | null;
|
||||
transport: "frontend";
|
||||
command: string | null;
|
||||
}
|
||||
|
||||
interface FrontendSession {
|
||||
baseUrl: string;
|
||||
cookie: string;
|
||||
@@ -38,6 +49,19 @@ interface FetchJsonResult {
|
||||
responseContentLength?: string | null;
|
||||
}
|
||||
|
||||
class RemoteCliFailure extends Error {
|
||||
readonly failureClassification: RemoteFailureClassification;
|
||||
readonly runnerDisposition = "infra-blocked";
|
||||
readonly detail: unknown;
|
||||
|
||||
constructor(failureClassification: RemoteFailureClassification, message: string, detail?: unknown) {
|
||||
super(message);
|
||||
this.name = "RemoteCliFailure";
|
||||
this.failureClassification = failureClassification;
|
||||
this.detail = detail ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
const hostOptions = new Set(["--main-server-ip", "--main-server", "--server"]);
|
||||
const userOptions = new Set(["--main-server-user", "--server-user"]);
|
||||
const portOptions = new Set(["--main-server-port", "--server-port"]);
|
||||
@@ -62,6 +86,55 @@ function transportValue(raw: string, option: string): RemoteCliOptions["transpor
|
||||
throw new Error(`${option} must be one of: auto, frontend, ssh`);
|
||||
}
|
||||
|
||||
function truthyDisabled(raw: string | undefined): boolean {
|
||||
if (raw === undefined) return false;
|
||||
const normalized = raw.trim().toLowerCase();
|
||||
return normalized === "0" || normalized === "false" || normalized === "off" || normalized === "disabled";
|
||||
}
|
||||
|
||||
function normalizeRemoteHostHint(raw: string | undefined): string | null {
|
||||
const value = raw?.trim() ?? "";
|
||||
if (value.length === 0) return null;
|
||||
if (value === "localhost" || value === "127.0.0.1" || value === "::1") return null;
|
||||
return value.replace(/\/+$/u, "");
|
||||
}
|
||||
|
||||
function isCodeQueueRunnerEnv(env: NodeJS.ProcessEnv): boolean {
|
||||
return Boolean(env.CODE_QUEUE_SERVICE_ROLE || env.CODE_QUEUE_INSTANCE_ID || env.CODE_QUEUE_DEV_CONTAINER_MASTER_HOST || env.KUBERNETES_SERVICE_HOST);
|
||||
}
|
||||
|
||||
export function autoRemoteCiPublishUserServiceDryRunPlan(
|
||||
config: UniDeskConfig,
|
||||
args: string[],
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): AutoRemoteCiPublishPlan {
|
||||
const [top, sub] = args;
|
||||
const isPublishDryRun = top === "ci" && sub === "publish-user-service" && args.includes("--dry-run");
|
||||
if (!isPublishDryRun) {
|
||||
return { enabled: false, host: null, reason: "not ci publish-user-service --dry-run", failureClassification: null, transport: "frontend", command: null };
|
||||
}
|
||||
if (truthyDisabled(env.UNIDESK_CI_PUBLISH_AUTO_REMOTE)) {
|
||||
return { enabled: false, host: null, reason: "UNIDESK_CI_PUBLISH_AUTO_REMOTE disables automatic remote preflight", failureClassification: "local-docker-required", transport: "frontend", command: null };
|
||||
}
|
||||
const explicitHost = normalizeRemoteHostHint(env.CODE_QUEUE_DEV_CONTAINER_MASTER_HOST)
|
||||
?? normalizeRemoteHostHint(env.UNIDESK_MAIN_SERVER_IP)
|
||||
?? normalizeRemoteHostHint(env.UNIDESK_MAIN_SERVER_HOST);
|
||||
const host = explicitHost ?? (isCodeQueueRunnerEnv(env) ? normalizeRemoteHostHint(config.network.publicHost) : null);
|
||||
if (host === null) {
|
||||
return { enabled: false, host: null, reason: "no remote main-server frontend host was detected for this runner", failureClassification: "local-docker-required", transport: "frontend", command: null };
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
host,
|
||||
reason: explicitHost === null
|
||||
? "Code Queue runner environment detected; using config.network.publicHost as the frontend control-plane"
|
||||
: "runner remote main-server frontend host detected",
|
||||
failureClassification: null,
|
||||
transport: "frontend",
|
||||
command: ["bun", "scripts/cli.ts", "--main-server-ip", host, ...args].join(" "),
|
||||
};
|
||||
}
|
||||
|
||||
export function extractRemoteCliOptions(argv: string[]): RemoteCliOptions {
|
||||
const rest: string[] = [];
|
||||
const options: RemoteCliOptions = {
|
||||
@@ -168,7 +241,45 @@ function emitRemoteError(command: string, error: unknown): void {
|
||||
const payload = error instanceof Error
|
||||
? { name: error.name, message: error.message, stack: error.stack ?? null }
|
||||
: { message: String(error) };
|
||||
process.stdout.write(`${JSON.stringify({ ok: false, command, error: payload }, null, 2)}\n`);
|
||||
const classification = error instanceof RemoteCliFailure
|
||||
? {
|
||||
failureClassification: error.failureClassification,
|
||||
runnerDisposition: error.runnerDisposition,
|
||||
retryable: error.failureClassification !== "auth-missing",
|
||||
detail: error.detail,
|
||||
recoveryHints: remoteFailureRecoveryHints(error.failureClassification),
|
||||
}
|
||||
: null;
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
ok: false,
|
||||
command,
|
||||
...(classification === null ? {} : classification),
|
||||
error: payload,
|
||||
}, null, 2)}\n`);
|
||||
}
|
||||
|
||||
function remoteFailureRecoveryHints(classification: RemoteFailureClassification): string[] {
|
||||
if (classification === "auth-missing") {
|
||||
return [
|
||||
"verify config.json frontend auth.username/auth.password against the remote main-server frontend login",
|
||||
"retry the same command through --main-server-ip after credentials are restored",
|
||||
];
|
||||
}
|
||||
if (classification === "remote-proxy-missing") {
|
||||
return [
|
||||
"verify the remote frontend is reachable on the configured public frontend port",
|
||||
"verify frontend can proxy /api to backend-core before retrying artifact publish preflight",
|
||||
];
|
||||
}
|
||||
if (classification === "provider-unreachable") {
|
||||
return [
|
||||
"verify backend-core /api/dispatch can create D601 host.ssh tasks",
|
||||
"verify the D601 provider-gateway host.ssh capability and artifact registry health",
|
||||
];
|
||||
}
|
||||
return [
|
||||
"run this read-only preflight with --main-server-ip <host> from runner environments without local backend-core/database containers",
|
||||
];
|
||||
}
|
||||
|
||||
function commandName(args: string[]): string {
|
||||
@@ -238,17 +349,28 @@ async function readJson(url: string, init?: RequestInit, timeoutMs = 8000, maxRe
|
||||
|
||||
async function loginFrontend(host: string, config: UniDeskConfig): Promise<FrontendSession> {
|
||||
const baseUrl = frontendBaseUrl(host, config);
|
||||
const res = await fetch(`${baseUrl}/login`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ username: config.auth.username, password: config.auth.password }),
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), 8_000);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(`${baseUrl}/login`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ username: config.auth.username, password: config.auth.password }),
|
||||
signal: controller.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new RemoteCliFailure("remote-proxy-missing", `frontend login request failed via ${baseUrl}: ${error instanceof Error ? error.message : String(error)}`, { baseUrl });
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
const body = await res.text();
|
||||
if (!res.ok) {
|
||||
throw new Error(`frontend login failed via ${baseUrl}: status=${res.status} body=${body.slice(0, 300)}`);
|
||||
const failureClassification: RemoteFailureClassification = res.status === 401 || res.status === 403 ? "auth-missing" : "remote-proxy-missing";
|
||||
throw new RemoteCliFailure(failureClassification, `frontend login failed via ${baseUrl}: status=${res.status} body=${body.slice(0, 300)}`, { baseUrl, status: res.status });
|
||||
}
|
||||
const cookie = res.headers.get("set-cookie")?.split(";")[0] ?? "";
|
||||
if (cookie.length === 0) throw new Error(`frontend login via ${baseUrl} did not return a session cookie`);
|
||||
if (cookie.length === 0) throw new RemoteCliFailure("auth-missing", `frontend login via ${baseUrl} did not return a session cookie`, { baseUrl, status: res.status });
|
||||
return { baseUrl, cookie };
|
||||
}
|
||||
|
||||
@@ -606,6 +728,8 @@ async function remoteCi(session: FrontendSession, config: UniDeskConfig, args: s
|
||||
transport: "frontend",
|
||||
readonly: true,
|
||||
result: await runCiPublishUserServiceDryRunPreflight(config, args.slice(1), {
|
||||
kind: "remote-frontend",
|
||||
remoteHost: session.baseUrl,
|
||||
coreFetch: (path, init) => frontendJson(session, path, init === undefined ? undefined : {
|
||||
method: init.method,
|
||||
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
||||
|
||||
Reference in New Issue
Block a user