404 lines
12 KiB
TypeScript
404 lines
12 KiB
TypeScript
import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/diffs";
|
|
import {
|
|
DIFF_IMAGE_QUALITY_PRESETS,
|
|
DIFF_INDICATORS,
|
|
DIFF_LAYOUTS,
|
|
DIFF_MODES,
|
|
DIFF_OUTPUT_FORMATS,
|
|
DIFF_THEMES,
|
|
type DiffFileDefaults,
|
|
type DiffImageQualityPreset,
|
|
type DiffIndicators,
|
|
type DiffLayout,
|
|
type DiffMode,
|
|
type DiffOutputFormat,
|
|
type DiffPresentationDefaults,
|
|
type DiffTheme,
|
|
type DiffToolDefaults,
|
|
} from "./types.js";
|
|
|
|
type DiffsPluginConfig = {
|
|
defaults?: {
|
|
fontFamily?: string;
|
|
fontSize?: number;
|
|
lineSpacing?: number;
|
|
layout?: DiffLayout;
|
|
showLineNumbers?: boolean;
|
|
diffIndicators?: DiffIndicators;
|
|
wordWrap?: boolean;
|
|
background?: boolean;
|
|
theme?: DiffTheme;
|
|
fileFormat?: DiffOutputFormat;
|
|
fileQuality?: DiffImageQualityPreset;
|
|
fileScale?: number;
|
|
fileMaxWidth?: number;
|
|
format?: DiffOutputFormat;
|
|
// Backward-compatible aliases retained for existing configs.
|
|
imageFormat?: DiffOutputFormat;
|
|
imageQuality?: DiffImageQualityPreset;
|
|
imageScale?: number;
|
|
imageMaxWidth?: number;
|
|
mode?: DiffMode;
|
|
};
|
|
security?: {
|
|
allowRemoteViewer?: boolean;
|
|
};
|
|
};
|
|
|
|
const DEFAULT_IMAGE_QUALITY_PROFILES = {
|
|
standard: {
|
|
scale: 2,
|
|
maxWidth: 960,
|
|
maxPixels: 8_000_000,
|
|
},
|
|
hq: {
|
|
scale: 2.5,
|
|
maxWidth: 1200,
|
|
maxPixels: 14_000_000,
|
|
},
|
|
print: {
|
|
scale: 3,
|
|
maxWidth: 1400,
|
|
maxPixels: 24_000_000,
|
|
},
|
|
} as const satisfies Record<
|
|
DiffImageQualityPreset,
|
|
{ scale: number; maxWidth: number; maxPixels: number }
|
|
>;
|
|
|
|
export const DEFAULT_DIFFS_TOOL_DEFAULTS: DiffToolDefaults = {
|
|
fontFamily: "Fira Code",
|
|
fontSize: 15,
|
|
lineSpacing: 1.6,
|
|
layout: "unified",
|
|
showLineNumbers: true,
|
|
diffIndicators: "bars",
|
|
wordWrap: true,
|
|
background: true,
|
|
theme: "dark",
|
|
fileFormat: "png",
|
|
fileQuality: "standard",
|
|
fileScale: DEFAULT_IMAGE_QUALITY_PROFILES.standard.scale,
|
|
fileMaxWidth: DEFAULT_IMAGE_QUALITY_PROFILES.standard.maxWidth,
|
|
mode: "both",
|
|
};
|
|
|
|
export type DiffsPluginSecurityConfig = {
|
|
allowRemoteViewer: boolean;
|
|
};
|
|
|
|
export const DEFAULT_DIFFS_PLUGIN_SECURITY: DiffsPluginSecurityConfig = {
|
|
allowRemoteViewer: false,
|
|
};
|
|
|
|
const DIFFS_PLUGIN_CONFIG_JSON_SCHEMA = {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
properties: {
|
|
defaults: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
properties: {
|
|
fontFamily: { type: "string", default: DEFAULT_DIFFS_TOOL_DEFAULTS.fontFamily },
|
|
fontSize: {
|
|
type: "number",
|
|
minimum: 10,
|
|
maximum: 24,
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fontSize,
|
|
},
|
|
lineSpacing: {
|
|
type: "number",
|
|
minimum: 1,
|
|
maximum: 3,
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.lineSpacing,
|
|
},
|
|
layout: {
|
|
type: "string",
|
|
enum: [...DIFF_LAYOUTS],
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.layout,
|
|
},
|
|
showLineNumbers: {
|
|
type: "boolean",
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.showLineNumbers,
|
|
},
|
|
diffIndicators: {
|
|
type: "string",
|
|
enum: [...DIFF_INDICATORS],
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.diffIndicators,
|
|
},
|
|
wordWrap: { type: "boolean", default: DEFAULT_DIFFS_TOOL_DEFAULTS.wordWrap },
|
|
background: { type: "boolean", default: DEFAULT_DIFFS_TOOL_DEFAULTS.background },
|
|
theme: {
|
|
type: "string",
|
|
enum: [...DIFF_THEMES],
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.theme,
|
|
},
|
|
fileFormat: {
|
|
type: "string",
|
|
enum: [...DIFF_OUTPUT_FORMATS],
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileFormat,
|
|
},
|
|
format: {
|
|
type: "string",
|
|
enum: [...DIFF_OUTPUT_FORMATS],
|
|
},
|
|
fileQuality: {
|
|
type: "string",
|
|
enum: [...DIFF_IMAGE_QUALITY_PRESETS],
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality,
|
|
},
|
|
fileScale: {
|
|
type: "number",
|
|
minimum: 1,
|
|
maximum: 4,
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileScale,
|
|
},
|
|
fileMaxWidth: {
|
|
type: "number",
|
|
minimum: 640,
|
|
maximum: 2400,
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.fileMaxWidth,
|
|
},
|
|
imageFormat: {
|
|
type: "string",
|
|
enum: [...DIFF_OUTPUT_FORMATS],
|
|
},
|
|
imageQuality: {
|
|
type: "string",
|
|
enum: [...DIFF_IMAGE_QUALITY_PRESETS],
|
|
},
|
|
imageScale: {
|
|
type: "number",
|
|
minimum: 1,
|
|
maximum: 4,
|
|
},
|
|
imageMaxWidth: {
|
|
type: "number",
|
|
minimum: 640,
|
|
maximum: 2400,
|
|
},
|
|
mode: {
|
|
type: "string",
|
|
enum: [...DIFF_MODES],
|
|
default: DEFAULT_DIFFS_TOOL_DEFAULTS.mode,
|
|
},
|
|
},
|
|
},
|
|
security: {
|
|
type: "object",
|
|
additionalProperties: false,
|
|
properties: {
|
|
allowRemoteViewer: {
|
|
type: "boolean",
|
|
default: DEFAULT_DIFFS_PLUGIN_SECURITY.allowRemoteViewer,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
export const diffsPluginConfigSchema: OpenClawPluginConfigSchema = {
|
|
safeParse(value: unknown) {
|
|
if (value === undefined) {
|
|
return { success: true, data: undefined };
|
|
}
|
|
try {
|
|
return { success: true, data: resolveDiffsPluginDefaults(value) };
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: {
|
|
issues: [{ path: [], message: error instanceof Error ? error.message : String(error) }],
|
|
},
|
|
};
|
|
}
|
|
},
|
|
jsonSchema: DIFFS_PLUGIN_CONFIG_JSON_SCHEMA,
|
|
};
|
|
|
|
export function resolveDiffsPluginDefaults(config: unknown): DiffToolDefaults {
|
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
return { ...DEFAULT_DIFFS_TOOL_DEFAULTS };
|
|
}
|
|
|
|
const defaults = (config as DiffsPluginConfig).defaults;
|
|
if (!defaults || typeof defaults !== "object" || Array.isArray(defaults)) {
|
|
return { ...DEFAULT_DIFFS_TOOL_DEFAULTS };
|
|
}
|
|
|
|
const fileQuality = normalizeFileQuality(defaults.fileQuality ?? defaults.imageQuality);
|
|
const profile = DEFAULT_IMAGE_QUALITY_PROFILES[fileQuality];
|
|
|
|
return {
|
|
fontFamily: normalizeFontFamily(defaults.fontFamily),
|
|
fontSize: normalizeFontSize(defaults.fontSize),
|
|
lineSpacing: normalizeLineSpacing(defaults.lineSpacing),
|
|
layout: normalizeLayout(defaults.layout),
|
|
showLineNumbers: defaults.showLineNumbers !== false,
|
|
diffIndicators: normalizeDiffIndicators(defaults.diffIndicators),
|
|
wordWrap: defaults.wordWrap !== false,
|
|
background: defaults.background !== false,
|
|
theme: normalizeTheme(defaults.theme),
|
|
fileFormat: normalizeFileFormat(defaults.fileFormat ?? defaults.imageFormat ?? defaults.format),
|
|
fileQuality,
|
|
fileScale: normalizeFileScale(defaults.fileScale ?? defaults.imageScale, profile.scale),
|
|
fileMaxWidth: normalizeFileMaxWidth(
|
|
defaults.fileMaxWidth ?? defaults.imageMaxWidth,
|
|
profile.maxWidth,
|
|
),
|
|
mode: normalizeMode(defaults.mode),
|
|
};
|
|
}
|
|
|
|
export function resolveDiffsPluginSecurity(config: unknown): DiffsPluginSecurityConfig {
|
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
return { ...DEFAULT_DIFFS_PLUGIN_SECURITY };
|
|
}
|
|
|
|
const security = (config as DiffsPluginConfig).security;
|
|
if (!security || typeof security !== "object" || Array.isArray(security)) {
|
|
return { ...DEFAULT_DIFFS_PLUGIN_SECURITY };
|
|
}
|
|
|
|
return {
|
|
allowRemoteViewer: security.allowRemoteViewer === true,
|
|
};
|
|
}
|
|
|
|
export function toPresentationDefaults(defaults: DiffToolDefaults): DiffPresentationDefaults {
|
|
const {
|
|
fontFamily,
|
|
fontSize,
|
|
lineSpacing,
|
|
layout,
|
|
showLineNumbers,
|
|
diffIndicators,
|
|
wordWrap,
|
|
background,
|
|
theme,
|
|
} = defaults;
|
|
return {
|
|
fontFamily,
|
|
fontSize,
|
|
lineSpacing,
|
|
layout,
|
|
showLineNumbers,
|
|
diffIndicators,
|
|
wordWrap,
|
|
background,
|
|
theme,
|
|
};
|
|
}
|
|
|
|
function normalizeFontFamily(fontFamily?: string): string {
|
|
const normalized = fontFamily?.trim();
|
|
return normalized || DEFAULT_DIFFS_TOOL_DEFAULTS.fontFamily;
|
|
}
|
|
|
|
function normalizeFontSize(fontSize?: number): number {
|
|
if (fontSize === undefined || !Number.isFinite(fontSize)) {
|
|
return DEFAULT_DIFFS_TOOL_DEFAULTS.fontSize;
|
|
}
|
|
const rounded = Math.floor(fontSize);
|
|
return Math.min(Math.max(rounded, 10), 24);
|
|
}
|
|
|
|
function normalizeLineSpacing(lineSpacing?: number): number {
|
|
if (lineSpacing === undefined || !Number.isFinite(lineSpacing)) {
|
|
return DEFAULT_DIFFS_TOOL_DEFAULTS.lineSpacing;
|
|
}
|
|
return Math.min(Math.max(lineSpacing, 1), 3);
|
|
}
|
|
|
|
function normalizeLayout(layout?: DiffLayout): DiffLayout {
|
|
return layout && DIFF_LAYOUTS.includes(layout) ? layout : DEFAULT_DIFFS_TOOL_DEFAULTS.layout;
|
|
}
|
|
|
|
function normalizeDiffIndicators(diffIndicators?: DiffIndicators): DiffIndicators {
|
|
return diffIndicators && DIFF_INDICATORS.includes(diffIndicators)
|
|
? diffIndicators
|
|
: DEFAULT_DIFFS_TOOL_DEFAULTS.diffIndicators;
|
|
}
|
|
|
|
function normalizeTheme(theme?: DiffTheme): DiffTheme {
|
|
return theme && DIFF_THEMES.includes(theme) ? theme : DEFAULT_DIFFS_TOOL_DEFAULTS.theme;
|
|
}
|
|
|
|
function normalizeFileFormat(fileFormat?: DiffOutputFormat): DiffOutputFormat {
|
|
return fileFormat && DIFF_OUTPUT_FORMATS.includes(fileFormat)
|
|
? fileFormat
|
|
: DEFAULT_DIFFS_TOOL_DEFAULTS.fileFormat;
|
|
}
|
|
|
|
function normalizeFileQuality(fileQuality?: DiffImageQualityPreset): DiffImageQualityPreset {
|
|
return fileQuality && DIFF_IMAGE_QUALITY_PRESETS.includes(fileQuality)
|
|
? fileQuality
|
|
: DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality;
|
|
}
|
|
|
|
function normalizeFileScale(fileScale: number | undefined, fallback: number): number {
|
|
if (fileScale === undefined || !Number.isFinite(fileScale)) {
|
|
return fallback;
|
|
}
|
|
const rounded = Math.round(fileScale * 100) / 100;
|
|
return Math.min(Math.max(rounded, 1), 4);
|
|
}
|
|
|
|
function normalizeFileMaxWidth(fileMaxWidth: number | undefined, fallback: number): number {
|
|
if (fileMaxWidth === undefined || !Number.isFinite(fileMaxWidth)) {
|
|
return fallback;
|
|
}
|
|
const rounded = Math.round(fileMaxWidth);
|
|
return Math.min(Math.max(rounded, 640), 2400);
|
|
}
|
|
|
|
function normalizeMode(mode?: DiffMode): DiffMode {
|
|
return mode && DIFF_MODES.includes(mode) ? mode : DEFAULT_DIFFS_TOOL_DEFAULTS.mode;
|
|
}
|
|
|
|
export function resolveDiffImageRenderOptions(params: {
|
|
defaults: DiffFileDefaults;
|
|
fileFormat?: DiffOutputFormat;
|
|
format?: DiffOutputFormat;
|
|
fileQuality?: DiffImageQualityPreset;
|
|
fileScale?: number;
|
|
fileMaxWidth?: number;
|
|
imageFormat?: DiffOutputFormat;
|
|
imageQuality?: DiffImageQualityPreset;
|
|
imageScale?: number;
|
|
imageMaxWidth?: number;
|
|
}): {
|
|
format: DiffOutputFormat;
|
|
qualityPreset: DiffImageQualityPreset;
|
|
scale: number;
|
|
maxWidth: number;
|
|
maxPixels: number;
|
|
} {
|
|
const format = normalizeFileFormat(
|
|
params.fileFormat ?? params.imageFormat ?? params.format ?? params.defaults.fileFormat,
|
|
);
|
|
const qualityOverrideProvided =
|
|
params.fileQuality !== undefined || params.imageQuality !== undefined;
|
|
const qualityPreset = normalizeFileQuality(
|
|
params.fileQuality ?? params.imageQuality ?? params.defaults.fileQuality,
|
|
);
|
|
const profile = DEFAULT_IMAGE_QUALITY_PROFILES[qualityPreset];
|
|
|
|
const scale = normalizeFileScale(
|
|
params.fileScale ?? params.imageScale,
|
|
qualityOverrideProvided ? profile.scale : params.defaults.fileScale,
|
|
);
|
|
const maxWidth = normalizeFileMaxWidth(
|
|
params.fileMaxWidth ?? params.imageMaxWidth,
|
|
qualityOverrideProvided ? profile.maxWidth : params.defaults.fileMaxWidth,
|
|
);
|
|
|
|
return {
|
|
format,
|
|
qualityPreset,
|
|
scale,
|
|
maxWidth,
|
|
maxPixels: profile.maxPixels,
|
|
};
|
|
}
|