Files
homarr/packages/api/src/router/custom-widget/legacy-migration.ts
T

74 lines
2.7 KiB
TypeScript

import type { legacyCustomWidgetDefinitions } from "@homarr/db/schema";
import { buildCustomWidgetAiPrompt } from "@homarr/custom-widgets/authoring-prompt";
import { isRecord } from "@homarr/common";
type LegacyCustomWidgetDefinition = typeof legacyCustomWidgetDefinitions.$inferSelect;
export function buildLegacyCustomWidgetMigrationPrompt(
definition: LegacyCustomWidgetDefinition,
configuredSecretKinds: readonly string[],
) {
const legacyDefinition = {
$schema: "homarr-custom-widget-v1",
name: definition.name,
description: definition.description,
iconUrl: definition.iconUrl ? redactLegacyUrl(definition.iconUrl) : null,
url: redactLegacyUrl(definition.url),
authType: definition.authType,
headerName: definition.headerName,
method: definition.method,
requestBody: redactLegacyRequestBody(definition.requestBody),
displayType: definition.displayType,
displayConfig: {
migrationNote:
"The preserved v1 display configuration was intentionally omitted because legacy free-text fields may contain credentials. Reconstruct it from the display type and visible behavior.",
},
configuredSecretKinds,
};
return buildCustomWidgetAiPrompt(
undefined,
undefined,
legacyDefinition,
[
"Migrate this preserved Homarr v1 Custom Widget to Custom JSX v2.",
"Preserve its visible behavior and API intent, use a source named default, and return importable v2 JSON plus JSX.",
"The URL and request-body values may be redacted; keep placeholders and never invent or include credentials.",
"Credential kinds are informational only and must be configured separately in Homarr.",
].join(" "),
);
}
function redactLegacyUrl(value: string) {
try {
const url = new URL(value);
url.username = "";
url.password = "";
url.hash = "";
url.pathname = url.pathname
.split("/")
.map((segment) => (segment ? "[REDACTED]" : segment))
.join("/");
for (const key of url.searchParams.keys()) url.searchParams.set(key, "[REDACTED]");
return url.toString();
} catch {
return "[INVALID LEGACY URL — enter the API URL in Homarr]";
}
}
function redactLegacyRequestBody(value: string | null) {
if (!value) return value;
try {
return JSON.stringify(redactAllValues(JSON.parse(value) as unknown));
} catch {
return "[REDACTED LEGACY REQUEST BODY]";
}
}
function redactAllValues(value: unknown): unknown {
if (Array.isArray(value)) return value.map(redactAllValues);
if (isRecord(value))
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, redactAllValues(child)]));
if (value === null) return null;
return "[REDACTED VALUE]";
}