Files
xo-cli/src/utils/template-utils.ts
2026-05-04 05:04:28 +00:00

254 lines
6.4 KiB
TypeScript

/**
* Template utility functions.
*
* Pure functions for parsing and formatting template data.
* These functions have no React dependencies and can be used
* in both TUI and CLI contexts.
*/
import type { XOTemplate, XOTemplateAction } from "@xo-cash/types";
/**
* Formatted template list item data.
*/
export interface FormattedTemplateItem {
/** The display label for the template */
label: string;
/** The template description */
description?: string;
/** Whether the template data is valid */
isValid: boolean;
}
/**
* Formatted action list item data.
*/
export interface FormattedActionItem {
/** The display label for the action */
label: string;
/** The action description */
description?: string;
/** Number of roles that can start this action */
roleCount: number;
/** Whether the action data is valid */
isValid: boolean;
}
/**
* A unique starting action (deduplicated by action identifier).
* Multiple roles that can start the same action are counted
* but not shown as separate entries.
*/
export interface UniqueStartingAction {
actionIdentifier: string;
name: string;
description?: string;
roleCount: number;
}
/**
* Role information from a template.
*/
export interface TemplateRole {
roleId: string;
name: string;
description?: string;
}
/**
* Format a template for display in a list.
*
* @param template - The template to format
* @param index - Optional index for numbered display
* @returns Formatted item data for display
*/
export function formatTemplateListItem(
template: XOTemplate | null | undefined,
index?: number,
): FormattedTemplateItem {
if (!template) {
return {
label: "",
description: undefined,
isValid: false,
};
}
const name = template.name || "Unnamed Template";
const prefix = index !== undefined ? `${index + 1}. ` : "";
return {
label: `${prefix}${name}`,
description: template.description,
isValid: true,
};
}
/**
* Format an action for display in a list.
*
* @param actionId - The action identifier
* @param action - The action definition from the template
* @param roleCount - Number of roles that can start this action
* @param index - Optional index for numbered display
* @returns Formatted item data for display
*/
export function formatActionListItem(
actionId: string,
action: XOTemplateAction | null | undefined,
roleCount: number = 1,
index?: number,
): FormattedActionItem {
if (!actionId) {
return {
label: "",
description: undefined,
roleCount: 0,
isValid: false,
};
}
const name = action?.name || actionId;
const prefix = index !== undefined ? `${index + 1}. ` : "";
const roleSuffix = roleCount > 1 ? ` (${roleCount} roles)` : "";
return {
label: `${prefix}${name}${roleSuffix}`,
description: action?.description,
roleCount,
isValid: true,
};
}
/**
* Deduplicate starting actions from a template.
* Multiple roles that can start the same action are counted
* but returned as a single entry.
*
* @param template - The template to process
* @param startingActions - Array of { action, role } pairs
* @returns Array of unique starting actions with role counts
*/
export function deduplicateStartingActions(
template: XOTemplate,
startingActions: Array<{ action: string; role: string }>,
): UniqueStartingAction[] {
const actionMap = new Map<string, UniqueStartingAction>();
for (const sa of startingActions) {
if (actionMap.has(sa.action)) {
actionMap.get(sa.action)!.roleCount++;
} else {
const actionDef = template.actions?.[sa.action];
actionMap.set(sa.action, {
actionIdentifier: sa.action,
name: actionDef?.name || sa.action,
description: actionDef?.description,
roleCount: 1,
});
}
}
return Array.from(actionMap.values());
}
/**
* Get all roles from a template.
*
* @param template - The template to process
* @returns Array of role information
*/
export function getTemplateRoles(template: XOTemplate): TemplateRole[] {
if (!template.roles) return [];
return Object.entries(template.roles).map(([roleId, role]) => {
// Handle case where role might be a string instead of object
const roleObj = typeof role === "object" ? role : null;
return {
roleId,
name: roleObj?.name || roleId,
description: roleObj?.description,
};
});
}
/**
* Get roles that can start a specific action.
*
* @param template - The template to check
* @param actionIdentifier - The action to check
* @returns Array of role information for roles that can start this action
*/
export function getRolesForAction(
template: XOTemplate,
actionIdentifier: string,
): TemplateRole[] {
const startEntries = (template.start ?? []).filter(
(s) => s.action === actionIdentifier,
);
return startEntries.map((entry) => {
const roleDef = template.roles?.[entry.role || ''];
const roleObj = typeof roleDef === "object" ? roleDef : null;
// TODO: This is ugly. Lot of conditionals. Need to take a much closer look at this.
return {
roleId: entry.role || '',
name: roleObj?.name || entry.role || '',
description: roleObj?.description,
};
});
}
/**
* Get template name safely.
*
* @param template - The template
* @returns The template name or a default
*/
export function getTemplateName(
template: XOTemplate | null | undefined,
): string {
return template?.name || "Unknown Template";
}
/**
* Get template description safely.
*
* @param template - The template
* @returns The template description or undefined
*/
export function getTemplateDescription(
template: XOTemplate | null | undefined,
): string | undefined {
return template?.description;
}
/**
* Get action name safely.
*
* @param template - The template containing the action
* @param actionId - The action identifier
* @returns The action name or the action ID as fallback
*/
export function getActionName(
template: XOTemplate | null | undefined,
actionId: string,
): string {
return template?.actions?.[actionId]?.name || actionId;
}
/**
* Get action description safely.
*
* @param template - The template containing the action
* @param actionId - The action identifier
* @returns The action description or undefined
*/
export function getActionDescription(
template: XOTemplate | null | undefined,
actionId: string,
): string | undefined {
return template?.actions?.[actionId]?.description;
}