feat: add CI deployment and live progress

This commit is contained in:
2026-09-05 12:09:16 +00:00 Unverified
parent 321f4e807a
commit aa02826dbb
27 changed files with 2711 additions and 180 deletions
+107 -15
View File
@@ -98,7 +98,20 @@ export type CredentialMetadata = {
secretName?: string;
};
export type OperationExecution = { signal?: AbortSignal };
export type OperationProgressEmitter = (event: {
resource: {
apiVersion: string;
kind: string;
name: string;
namespace?: string;
};
phase: "apply" | "wait" | "delete";
state: "started" | "succeeded" | "failed" | "aborted";
}) => Promise<void>;
export type OperationExecution = {
signal?: AbortSignal;
emit?: OperationProgressEmitter;
};
export type ManagementDependencies = {
readNamespace(project: string): Promise<NamespaceRecord | undefined>;
@@ -222,6 +235,20 @@ function resourceName(resource: KubernetesObject): string {
return name;
}
function resourceProgressIdentity(
resource: Pick<
ResourceIdentity,
"apiVersion" | "kind" | "name" | "namespace"
>,
) {
return {
apiVersion: resource.apiVersion,
kind: resource.kind,
name: resource.name,
...(resource.namespace && { namespace: resource.namespace }),
};
}
function assertResourceOwnership(
workspace: Workspace,
resource: KubernetesObject,
@@ -426,8 +453,22 @@ export function createManagementService(dependencies: ManagementDependencies) {
}
}
for (const resource of resources) {
throwIfExecutionAborted(execution);
await dependencies.deleteResource(resource, execution);
const event = {
resource: resourceProgressIdentity(resource),
phase: "delete" as const,
};
try {
throwIfExecutionAborted(execution);
await execution?.emit?.({ ...event, state: "started" });
await dependencies.deleteResource(resource, execution);
await execution?.emit?.({ ...event, state: "succeeded" });
} catch (error) {
await execution?.emit?.({
...event,
state: execution?.signal?.aborted ? "aborted" : "failed",
});
throw error;
}
}
}
@@ -522,7 +563,12 @@ export function createManagementService(dependencies: ManagementDependencies) {
}
for (const name of selected) {
throwIfExecutionAborted(execution);
await dependencies.scaleDeployment(workspace.project, name, 0, execution);
await dependencies.scaleDeployment(
workspace.project,
name,
0,
execution,
);
}
return selected;
},
@@ -535,7 +581,11 @@ export function createManagementService(dependencies: ManagementDependencies) {
const selected = await targets(workspace, names);
for (const name of selected) {
throwIfExecutionAborted(execution);
await dependencies.restartDeployment(workspace.project, name, execution);
await dependencies.restartDeployment(
workspace.project,
name,
execution,
);
}
return selected;
},
@@ -554,7 +604,11 @@ export function createManagementService(dependencies: ManagementDependencies) {
);
for (const candidate of candidates) {
throwIfExecutionAborted(execution);
await dependencies.rollbackDeployment(workspace.project, candidate, execution);
await dependencies.rollbackDeployment(
workspace.project,
candidate,
execution,
);
}
for (const candidate of candidates) {
throwIfExecutionAborted(execution);
@@ -710,8 +764,27 @@ export function createManagementService(dependencies: ManagementDependencies) {
for (const resource of sortResources(
resources.map((item) => labelDesired(workspace, item)),
)) {
throwIfExecutionAborted(execution);
applied.push(await dependencies.applyResource(resource, execution));
const event = {
resource: resourceProgressIdentity({
apiVersion: resource.apiVersion!,
kind: resource.kind!,
name: resourceName(resource),
namespace: resource.metadata?.namespace,
}),
phase: "apply" as const,
};
try {
throwIfExecutionAborted(execution);
await execution?.emit?.({ ...event, state: "started" });
applied.push(await dependencies.applyResource(resource, execution));
await execution?.emit?.({ ...event, state: "succeeded" });
} catch (error) {
await execution?.emit?.({
...event,
state: execution?.signal?.aborted ? "aborted" : "failed",
});
throw error;
}
}
return applied;
},
@@ -724,13 +797,32 @@ export function createManagementService(dependencies: ManagementDependencies) {
): Promise<void> {
const selected = await targets(workspace, deploymentTargets);
for (const name of selected) {
throwIfExecutionAborted(execution);
await dependencies.waitForDeployment(
workspace.project,
name,
timeoutMs,
execution,
);
const event = {
resource: {
apiVersion: "apps/v1",
kind: "Deployment",
name,
namespace: workspace.project,
},
phase: "wait" as const,
};
try {
throwIfExecutionAborted(execution);
await execution?.emit?.({ ...event, state: "started" });
await dependencies.waitForDeployment(
workspace.project,
name,
timeoutMs,
execution,
);
await execution?.emit?.({ ...event, state: "succeeded" });
} catch (error) {
await execution?.emit?.({
...event,
state: execution?.signal?.aborted ? "aborted" : "failed",
});
throw error;
}
}
},