fix: add safe frontend rebuild workflow
This commit is contained in:
+9
-1
@@ -1,6 +1,6 @@
|
||||
import { readConfig } from "./src/config";
|
||||
import { debugDispatch, debugHealth, debugTask, isDebugDispatchCommand, type DebugDispatchCommand } from "./src/debug";
|
||||
import { stackLogs, stackStatus, startStack, stopStack } from "./src/docker";
|
||||
import { isRebuildableService, rebuildService, stackLogs, stackStatus, startStack, stopStack } from "./src/docker";
|
||||
import { runE2E } from "./src/e2e";
|
||||
import { emitError, emitJson } from "./src/output";
|
||||
import { jobWithTail, listJobs, readJob, runJob } from "./src/jobs";
|
||||
@@ -25,6 +25,7 @@ function help(): unknown {
|
||||
{ command: "server stop", description: "Fire-and-forget docker-compose down for the fixed UniDesk stack." },
|
||||
{ command: "server status", description: "Show fixed ports, containers, service health, and public URLs." },
|
||||
{ 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: "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." },
|
||||
@@ -141,6 +142,13 @@ async function main(): Promise<void> {
|
||||
emitJson(commandName, stackLogs(config, numberOption("--tail-bytes", 3000)));
|
||||
return;
|
||||
}
|
||||
if (sub === "rebuild") {
|
||||
if (!isRebuildableService(third)) {
|
||||
throw new Error("server rebuild requires one of: backend-core, frontend, provider-gateway");
|
||||
}
|
||||
emitJson(commandName, rebuildService(config, third));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (top === "job") {
|
||||
|
||||
@@ -18,6 +18,13 @@ export interface ContainerStatus {
|
||||
ports: string;
|
||||
}
|
||||
|
||||
const rebuildableServices = ["backend-core", "frontend", "provider-gateway"] as const;
|
||||
export type RebuildableService = typeof rebuildableServices[number];
|
||||
|
||||
export function isRebuildableService(value: string | undefined): value is RebuildableService {
|
||||
return rebuildableServices.some((service) => service === value);
|
||||
}
|
||||
|
||||
export function resolveComposeCommand(config: UniDeskConfig, envFile: string): string[] {
|
||||
const composeFile = rootPath(config.docker.composeFile);
|
||||
if (commandOk(["docker", "compose", "version"], repoRoot)) {
|
||||
@@ -133,6 +140,47 @@ export function stopStack(config: UniDeskConfig): unknown {
|
||||
return { job, runtimeEnv, command, portsBeforeStop: fixedPorts(config) };
|
||||
}
|
||||
|
||||
export function rebuildService(config: UniDeskConfig, service: RebuildableService): unknown {
|
||||
const runtimeEnv = writeComposeEnv(config, false);
|
||||
const compose = resolveComposeCommand(config, runtimeEnv.envFile);
|
||||
const buildCommand = [...compose, "build", service];
|
||||
const listServiceContainersCommand = [
|
||||
"docker",
|
||||
"ps",
|
||||
"-aq",
|
||||
"--filter",
|
||||
`label=com.docker.compose.project=${config.docker.projectName}`,
|
||||
"--filter",
|
||||
`label=com.docker.compose.service=${service}`,
|
||||
];
|
||||
const upCommand = [...compose, "up", "-d", "--no-deps", service];
|
||||
const script = [
|
||||
"set -euo pipefail",
|
||||
`echo ${shellJoin(["rebuild_service", service, "build_first_then_replace_container"])}`,
|
||||
shellJoin(buildCommand),
|
||||
`ids=$(${shellJoin(listServiceContainersCommand)})`,
|
||||
`if [ -n "$ids" ]; then echo "remove_existing_compose_service_containers service=${service} ids=$ids"; docker rm -f $ids; else echo "no_existing_compose_service_containers service=${service}"; fi`,
|
||||
shellJoin(upCommand),
|
||||
].join("; ");
|
||||
const command = ["bash", "-lc", script];
|
||||
const job = startJob("server_rebuild", command, `Rebuild and replace UniDesk ${service} without Docker Compose v1 recreate`);
|
||||
return {
|
||||
job,
|
||||
runtimeEnv,
|
||||
service,
|
||||
command,
|
||||
strategy: {
|
||||
buildBeforeRemove: true,
|
||||
removeScope: {
|
||||
projectLabel: config.docker.projectName,
|
||||
serviceLabel: service,
|
||||
},
|
||||
noDeps: true,
|
||||
namedVolumesPreserved: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function fixedPorts(config: UniDeskConfig): Array<{ name: string; port: number; listening: boolean }> {
|
||||
return [
|
||||
{ name: "frontend", port: config.network.frontend.port, listening: isPortListening(config.network.frontend.port) },
|
||||
|
||||
Reference in New Issue
Block a user