fix: simplify custom widget package workbench

This commit is contained in:
ajnart committed 2026-09-12 15:33:51 +02:00
1 parent 9a52a035aa
commit 48042a08ff
37 files changed
+1155 -608

No files matched your search

@@ -90,6 +90,8 @@ export const widgetCollectionContentsSchema = z
else if (!shared) issue(["connections", name], `collection slot '${slot}' is undeclared`);
else if (!isWidgetConnectionCompatible(requirement, shared))
issue(["connections", name], `slot '${slot}' requires compatible ${requirement.kind} settings`);
else if (Boolean(requirement.multiple) !== Boolean(shared.multiple))
issue(["connections", name], `slot '${slot}' must use the same repeatable connection setting`);
else if (!requirement.optional && shared.optional)
issue(["connections", name], `slot '${slot}' must be required because this widget requires it`);
}
@@ -76,6 +76,17 @@ export class WidgetPackageProcess {
public get stopped() {
return this.dead;
}
public inspect() {
let state: "running" | "stopped" = "running";
if (this.dead) state = "stopped";
return {
state,
pid: this.child.pid ?? null,
activeRequests: this.pending.size,
queuedRequests: this.queued,
lastUsed: this.lastUsed,
};
}
public waitUntilReady() {
return this.ready;
}
+32 -2
View File
@@ -79,6 +79,9 @@ export const widgetPackageConnectionSchema = z
label: z.string().trim().min(1).max(128),
kind: z.enum(["http", "integration", "service"]),
serviceType: identifier.optional(),
integrationKind: identifier.optional(),
// Optional fields preserve the digest of existing package archives.
multiple: z.boolean().optional(),
optional: z.boolean().default(false),
description: z.string().max(2048).optional(),
})
@@ -89,14 +92,41 @@ export const widgetPackageConnectionSchema = z
/** Shared by binding pickers, activation and portable collection imports. */
export function isWidgetConnectionCompatible(
requirement: { kind: string; serviceType?: string },
configuration: { kind?: unknown; serviceType?: unknown },
requirement: { kind: string; serviceType?: string; integrationKind?: string },
configuration: { kind?: unknown; serviceType?: unknown; integrationKind?: unknown },
) {
if (requirement.kind !== configuration.kind) return false;
if (requirement.kind === "service") return requirement.serviceType === configuration.serviceType;
if (requirement.kind === "integration" && requirement.integrationKind)
return requirement.integrationKind === configuration.integrationKind;
return true;
}
/** Repeatable connections retain the original named binding and add stable, namespaced members.
* A colon cannot appear in a manifest connection name, so groups cannot overlap.
*/
export function getWidgetConnectionBindingNames(
name: string,
requirement: { multiple?: boolean },
bindings: Record<string, string>,
) {
return Object.keys(bindings).filter(
(key) => Boolean(bindings[key]) && (key === name || (requirement.multiple && key.startsWith(`${name}:`))),
);
}
export function getWidgetConnectionRequirement<T extends { multiple?: boolean }>(
requirements: Record<string, T>,
name: string,
) {
const exact = requirements[name];
if (exact) return exact;
const prefix = name.split(":")[0];
const group = prefix && requirements[prefix];
if (group && group.multiple) return group;
return undefined;
}
export const customWidgetPackageSchema = z
.strictObject({
$schema: z.literal(CUSTOM_WIDGET_PACKAGE_SCHEMA),
@@ -100,6 +100,17 @@ export class CustomWidgetPackageSupervisor {
if (pending) (await pending.catch(() => undefined))?.stop();
}
/** Read only: inspecting an inactive artifact must never start its server. */
public async inspect(digest: string) {
const pending = this.processes.get(digest);
if (!pending) return { state: "idle" as const };
try {
return (await pending).inspect();
} catch (error) {
return { state: "failed" as const, error: error instanceof Error ? error.message : String(error) };
}
}
public async cancelInstances(instanceIds: string[], options: { actions?: boolean } = {}): Promise<void> {
for (const id of instanceIds) this.instanceGenerations.set(id, (this.instanceGenerations.get(id) ?? 0) + 1);
const ids = new Set(instanceIds);