a4f8226f3f
"What is running where" was answerable only by asking someone. Each tool now reports its own version on startup into a ToolVersion registry, shown on Pipeline → Machines. The worker's version moves out of config.json and into the assembly: a hand-maintained string on each machine is exactly the thing that goes stale, and a machine reporting a version it is not running is worse than no version at all. publish.ps1 stamps the git SHA via SourceRevisionId, so a deployed exe traces back to a commit (1.1.0+feb1c7b). The panel gains PANEL_VERSION, shown in its title and every diagnostics report, and pings on load. Staleness is derived rather than declared: the newest version seen for a tool is the benchmark, so updating one machine is what flags the rest. There is no expected-version list to keep in sync — which would have been one more thing to forget. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
3.7 KiB
TypeScript
105 lines
3.7 KiB
TypeScript
import { Prisma } from "@prisma/client";
|
|
import { z } from "zod";
|
|
import { db } from "@/lib/db";
|
|
|
|
/**
|
|
* Deployed-tool registry. Every studio tool reports its own version on
|
|
* startup, so "what is running where" is answered by the tools themselves
|
|
* rather than by a spreadsheet someone has to remember to update.
|
|
*
|
|
* Deliberately self-maintaining: nothing here declares what the *current*
|
|
* version ought to be. The newest version seen for a tool is treated as the
|
|
* benchmark, so updating one machine is what flags the others as stale.
|
|
*/
|
|
|
|
export const TOOL_LABELS: Record<string, string> = {
|
|
AE_PANEL: "AE connector panel",
|
|
RENDER_WORKER: "Render worker",
|
|
};
|
|
|
|
export const toolPingSchema = z.object({
|
|
tool: z.enum(["AE_PANEL", "RENDER_WORKER"]),
|
|
machineName: z.string().min(1).max(200),
|
|
version: z.string().min(1).max(50),
|
|
buildRef: z.string().max(100).optional().nullable(),
|
|
hostUser: z.string().max(200).optional().nullable(),
|
|
hostApp: z.string().max(200).optional().nullable(),
|
|
details: z.record(z.string(), z.unknown()).optional(),
|
|
});
|
|
|
|
export type ToolPing = z.infer<typeof toolPingSchema>;
|
|
|
|
export async function recordToolVersion(input: ToolPing) {
|
|
const now = new Date();
|
|
const row = await db.toolVersion.upsert({
|
|
where: { tool_machineName: { tool: input.tool, machineName: input.machineName } },
|
|
create: {
|
|
tool: input.tool,
|
|
machineName: input.machineName,
|
|
version: input.version,
|
|
buildRef: input.buildRef ?? null,
|
|
hostUser: input.hostUser ?? null,
|
|
hostApp: input.hostApp ?? null,
|
|
details: (input.details ?? undefined) as Prisma.InputJsonValue | undefined,
|
|
lastSeenAt: now,
|
|
},
|
|
update: {
|
|
version: input.version,
|
|
buildRef: input.buildRef ?? null,
|
|
hostUser: input.hostUser ?? null,
|
|
hostApp: input.hostApp ?? null,
|
|
details: (input.details ?? undefined) as Prisma.InputJsonValue | undefined,
|
|
lastSeenAt: now,
|
|
},
|
|
});
|
|
return { tool: row.tool, machineName: row.machineName, version: row.version, lastSeenAt: row.lastSeenAt };
|
|
}
|
|
|
|
/**
|
|
* Compares versions as dot-separated numbers, falling back to a string
|
|
* compare for anything non-numeric. Good enough to answer "is this one
|
|
* behind?", which is the only question being asked.
|
|
*/
|
|
export function compareVersions(a: string, b: string): number {
|
|
const parse = (v: string) => v.split(/[.+-]/).map((p) => (/^\d+$/.test(p) ? Number(p) : p));
|
|
const pa = parse(a);
|
|
const pb = parse(b);
|
|
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
const x = pa[i] ?? 0;
|
|
const y = pb[i] ?? 0;
|
|
if (x === y) continue;
|
|
if (typeof x === "number" && typeof y === "number") return x < y ? -1 : 1;
|
|
return String(x) < String(y) ? -1 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
export async function listToolVersions() {
|
|
const rows = await db.toolVersion.findMany({ orderBy: [{ tool: "asc" }, { machineName: "asc" }] });
|
|
|
|
// Newest version seen per tool is the benchmark — update one machine and the
|
|
// rest light up as stale, with no "expected version" to keep in sync.
|
|
const newest = new Map<string, string>();
|
|
for (const row of rows) {
|
|
const current = newest.get(row.tool);
|
|
if (!current || compareVersions(row.version, current) > 0) newest.set(row.tool, row.version);
|
|
}
|
|
|
|
return {
|
|
tools: rows.map((row) => ({
|
|
id: row.id,
|
|
tool: row.tool,
|
|
toolLabel: TOOL_LABELS[row.tool] ?? row.tool,
|
|
machineName: row.machineName,
|
|
version: row.version,
|
|
buildRef: row.buildRef,
|
|
hostUser: row.hostUser,
|
|
hostApp: row.hostApp,
|
|
firstSeenAt: row.firstSeenAt,
|
|
lastSeenAt: row.lastSeenAt,
|
|
newestVersion: newest.get(row.tool) ?? row.version,
|
|
outdated: compareVersions(row.version, newest.get(row.tool) ?? row.version) < 0,
|
|
})),
|
|
};
|
|
}
|