@nitrogenbuilder/client-core
Version:
Nitrogen Builder Core Client
201 lines (177 loc) • 4.52 kB
text/typescript
import type {
Category,
ComponentManifest,
ComponentManifestCategory,
ComponentManifestChildren,
ComponentManifestEntry,
ComponentManifestGroup,
ComponentManifestOption,
ComponentManifestProp,
ComponentSettings,
Prop,
} from '@nitrogenbuilder/types';
export interface ComponentManifestSource {
name: string;
scope?: string;
settings: ComponentSettings;
}
type Serializable =
| null
| boolean
| number
| string
| Serializable[]
| { [key: string]: Serializable };
const PROP_COPY_KEYS: Array<keyof ComponentManifestProp> = [
'label',
'default',
'responsive',
'divider',
'clearable',
'conditions',
'allowOther',
'options',
'accept',
'ctrlType',
'useUnit',
'step',
'sources',
'dialogTitle',
'collection',
'initialLimitDefault',
'totalLimitDefault',
'loadMoreLimitDefault',
'useLoadMoreDefault',
'usePaginationDefault',
'config',
'hasToolbar',
'defaultLinkState',
'layout',
'layoutMode',
'startLabel',
'endLabel',
'min',
'max',
];
function sanitizeValue(value: unknown): Serializable | undefined {
if (value === undefined) return undefined;
if (
value === null ||
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
return value;
}
if (Array.isArray(value)) {
return value
.map((entry) => sanitizeValue(entry))
.filter((entry) => entry !== undefined) as Serializable[];
}
if (typeof value === 'function') {
return undefined;
}
if (typeof value === 'object') {
const next: Record<string, Serializable> = {};
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
const serialized = sanitizeValue(entry);
if (serialized !== undefined) {
next[key] = serialized;
}
}
return next;
}
return undefined;
}
function sanitizeOptions(options: unknown): ComponentManifestOption[] | undefined {
const serialized = sanitizeValue(options);
if (!Array.isArray(serialized)) return undefined;
return serialized as ComponentManifestOption[];
}
function normalizeProp(prop: Prop): ComponentManifestProp {
const next: ComponentManifestProp = {
type: prop.type,
};
for (const key of PROP_COPY_KEYS) {
const value = sanitizeValue((prop as Record<string, unknown>)[key]);
if (value !== undefined) {
if (key === 'options') {
next.options = sanitizeOptions(value);
continue;
}
(next as unknown as Record<string, unknown>)[key] = value;
}
}
if ('props' in prop && prop.props) {
next.props = normalizePropMap(prop.props);
}
if (prop.type === 'asset-folder' && 'files' in prop) {
next.meta = {
assetCount: Object.keys(prop.files || {}).length,
};
}
return next;
}
function normalizePropMap(props: Record<string, Prop>): Record<string, ComponentManifestProp> {
return Object.fromEntries(
Object.entries(props).map(([propKey, prop]) => [propKey, normalizeProp(prop)]),
);
}
function normalizeGroup(groupKey: string, group: Category['groups'][string]): ComponentManifestGroup {
return {
key: groupKey,
label: group.label,
props: normalizePropMap(group.props),
};
}
function normalizeCategory(categoryKey: string, category: Category): ComponentManifestCategory {
return {
key: categoryKey,
label: category.label,
groups: Object.fromEntries(
Object.entries(category.groups).map(([groupKey, group]) => [
groupKey,
normalizeGroup(groupKey, group),
]),
),
};
}
function normalizeChildren(children: unknown): ComponentManifestChildren | undefined {
const serialized = sanitizeValue(children);
if (serialized === undefined) return undefined;
return serialized as ComponentManifestChildren;
}
export function createComponentManifestEntry({
name,
scope,
settings,
}: ComponentManifestSource): ComponentManifestEntry {
return {
name,
scope,
description: settings.description,
sidebarCategory: settings.options?.category,
children: normalizeChildren(settings.options?.children),
categories: Object.fromEntries(
Object.entries(settings.categories).map(([categoryKey, category]) => [
categoryKey,
normalizeCategory(categoryKey, category),
]),
),
};
}
export function createComponentManifest(
components: ComponentManifestSource[],
options?: {
source?: string;
generatedAt?: string;
}
): ComponentManifest {
return {
components: [...components]
.sort((a, b) => a.name.localeCompare(b.name))
.map((component) => createComponentManifestEntry(component)),
generatedAt: options?.generatedAt ?? new Date().toISOString(),
source: options?.source,
};
}