[FRONT COMPONENTS] Move to twenty-sdk (#17587)

Move front components from twenty-shared to twenty-sdk
This commit is contained in:
Raphaël Bosi
2026-02-02 12:21:53 +00:00
committed by GitHub
parent b2218cbbf2
commit f0bc9fcb43
63 changed files with 146 additions and 139 deletions
@@ -1,159 +0,0 @@
/* eslint-disable no-console */
import * as prettier from '@prettier/sync';
import * as fs from 'fs';
import * as path from 'path';
import { IndentationText, Project, QuoteKind } from 'ts-morph';
import { ALLOWED_HTML_ELEMENTS } from '../../src/front-component-constants/AllowedHtmlElements';
import { COMMON_HTML_EVENTS } from '../../src/front-component-constants/CommonHtmlEvents';
import { EVENT_TO_REACT } from '../../src/front-component-constants/EventToReact';
import { HTML_COMMON_PROPERTIES } from '../../src/front-component-constants/HtmlCommonProperties';
import {
type ComponentSchema,
extractHtmlTag,
generateHostRegistry,
generateRemoteComponents,
generateRemoteElements,
HtmlElementConfigArrayZ,
OUTPUT_FILES,
} from './generators';
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
const PACKAGE_PATH = path.resolve(SCRIPT_DIR, '../..');
const FRONT_COMPONENT_PATH = path.join(PACKAGE_PATH, 'src/front-component');
const HOST_GENERATED_DIR = path.join(FRONT_COMPONENT_PATH, 'host/generated');
const REMOTE_GENERATED_DIR = path.join(
FRONT_COMPONENT_PATH,
'remote/generated',
);
const formatZodError = (error: {
issues: { path: PropertyKey[]; message: string }[];
}): string => {
return error.issues
.map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)
.join('\n');
};
const getHtmlElementSchemas = (): ComponentSchema[] => {
const result = HtmlElementConfigArrayZ.safeParse(ALLOWED_HTML_ELEMENTS);
if (!result.success) {
throw new Error(
`Invalid HTML element configuration:\n${formatZodError(result.error)}`,
);
}
return result.data.map((element) => ({
name: element.name,
tagName: element.name,
customElementName: element.tag,
properties: {
...HTML_COMMON_PROPERTIES,
...element.properties,
},
events: COMMON_HTML_EVENTS,
isHtmlElement: true,
htmlTag: extractHtmlTag(element.tag),
}));
};
const createProject = (): Project => {
return new Project({
manipulationSettings: {
indentationText: IndentationText.TwoSpaces,
quoteKind: QuoteKind.Single,
useTrailingCommas: true,
},
});
};
const writeGeneratedFile = (
dir: string,
filename: string,
content: string,
): void => {
const filePath = path.join(dir, filename);
const formattedContent = prettier.format(content, {
parser: 'typescript',
filepath: filePath,
singleQuote: true,
trailingComma: 'all',
endOfLine: 'lf',
});
fs.writeFileSync(filePath, formattedContent, 'utf-8');
console.log(`✓ Generated ${filePath}`);
};
const ensureDirectoriesExist = (): void => {
if (!fs.existsSync(HOST_GENERATED_DIR)) {
fs.mkdirSync(HOST_GENERATED_DIR, { recursive: true });
}
if (!fs.existsSync(REMOTE_GENERATED_DIR)) {
fs.mkdirSync(REMOTE_GENERATED_DIR, { recursive: true });
}
};
const main = (): void => {
console.log('📖 Generating remote DOM elements...\n');
let htmlElements: ComponentSchema[];
try {
htmlElements = getHtmlElementSchemas();
} catch (error) {
console.error('❌ Validation failed:', error);
process.exit(1);
}
console.log(`HTML Elements: ${htmlElements.length} elements`);
console.log(
` Tags: ${htmlElements.map((element) => element.htmlTag).join(', ')}`,
);
console.log(
` Events: ${COMMON_HTML_EVENTS.length} common events per element`,
);
console.log('');
ensureDirectoriesExist();
const project = createProject();
console.log('Host files:');
const hostRegistry = generateHostRegistry(
project,
htmlElements,
EVENT_TO_REACT,
);
writeGeneratedFile(
HOST_GENERATED_DIR,
OUTPUT_FILES.HOST_REGISTRY,
hostRegistry.getFullText(),
);
console.log('\nRemote files:');
const remoteElements = generateRemoteElements(
project,
htmlElements,
HTML_COMMON_PROPERTIES,
COMMON_HTML_EVENTS,
);
writeGeneratedFile(
REMOTE_GENERATED_DIR,
OUTPUT_FILES.REMOTE_ELEMENTS,
remoteElements.getFullText(),
);
const remoteComponents = generateRemoteComponents(project, htmlElements);
writeGeneratedFile(
REMOTE_GENERATED_DIR,
OUTPUT_FILES.REMOTE_COMPONENTS,
remoteComponents.getFullText(),
);
console.log('\n✅ All generated files created');
console.log(` Host: ${HOST_GENERATED_DIR}`);
console.log(` Remote: ${REMOTE_GENERATED_DIR}`);
};
main();
@@ -1,4 +0,0 @@
export const CUSTOM_ELEMENT_NAMES = {
ROOT: 'remote-root',
FRAGMENT: 'remote-fragment',
} as const;
@@ -1,4 +0,0 @@
export const INTERNAL_ELEMENT_CLASSES = {
ROOT: 'RemoteRootElement',
FRAGMENT: 'RemoteFragmentElement',
} as const;
@@ -1,5 +0,0 @@
export const OUTPUT_FILES = {
REMOTE_ELEMENTS: 'remote-elements.ts',
REMOTE_COMPONENTS: 'remote-components.ts',
HOST_REGISTRY: 'host-component-registry.ts',
} as const;
@@ -1,7 +0,0 @@
export const TYPE_NAMES = {
COMMON_PROPERTIES: 'HtmlCommonProperties',
COMMON_EVENTS: 'HtmlCommonEvents',
COMMON_PROPERTIES_CONFIG: 'HTML_COMMON_PROPERTIES_CONFIG',
COMMON_EVENTS_ARRAY: 'HTML_COMMON_EVENTS_ARRAY',
EMPTY_RECORD: 'Record<string, never>',
} as const;
@@ -1,4 +0,0 @@
export { CUSTOM_ELEMENT_NAMES } from './CustomElementNames';
export { INTERNAL_ELEMENT_CLASSES } from './InternalElementClasses';
export { OUTPUT_FILES } from './OutputFiles';
export { TYPE_NAMES } from './TypeNames';
@@ -1,120 +0,0 @@
import type { Project, SourceFile } from 'ts-morph';
import { CUSTOM_ELEMENT_NAMES } from './constants';
import { type ComponentSchema } from './schemas';
import { addFileHeader, addStatement } from './utils';
const generateRuntimeUtilities = (
eventToReactMapping: Record<string, string>,
): string => {
const eventMapEntries = Object.entries(eventToReactMapping)
.map(([domEvent, reactProp]) => ` on${domEvent}: '${reactProp}',`)
.join('\n');
return `const INTERNAL_PROPS = new Set(['element', 'receiver', 'components']);
const EVENT_NAME_MAP: Record<string, string> = {
${eventMapEntries}
};
const parseStyle = (styleString: string | undefined): React.CSSProperties | undefined => {
if (!styleString || typeof styleString !== 'string') {
return styleString as React.CSSProperties | undefined;
}
const style: Record<string, string> = {};
const declarations = styleString.split(';').filter(Boolean);
for (const declaration of declarations) {
const colonIndex = declaration.indexOf(':');
if (colonIndex === -1) continue;
const property = declaration.slice(0, colonIndex).trim();
const value = declaration.slice(colonIndex + 1).trim();
const camelProperty = property.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
style[camelProperty] = value;
}
return style;
};
const wrapEventHandler = (handler: () => void) => {
return (_event: unknown) => {
handler();
};
};
const filterProps = (props: Record<string, unknown>) => {
const filtered: Record<string, unknown> = {};
for (const [key, value] of Object.entries(props)) {
if (INTERNAL_PROPS.has(key) || value === undefined) continue;
if (key === 'style') {
filtered.style = parseStyle(value as string | undefined);
} else {
const normalizedKey = EVENT_NAME_MAP[key.toLowerCase()] || key;
if (normalizedKey.startsWith('on') && typeof value === 'function') {
filtered[normalizedKey] = wrapEventHandler(value as () => void);
} else {
filtered[normalizedKey] = value;
}
}
}
return filtered;
};`;
};
const generateWrapperComponent = (component: ComponentSchema): string => {
return `const ${component.name}Wrapper = ({ children, ...props }: { children?: React.ReactNode } & Record<string, unknown>) => {
return React.createElement('${component.htmlTag}', filterProps(props), children);
};`;
};
const generateRegistryMap = (components: ComponentSchema[]): string => {
const entries = components
.map(
(component) =>
` ['${component.customElementName}', createRemoteComponentRenderer(${component.name}Wrapper)],`,
)
.join('\n');
return `export const componentRegistry = new Map<string, ReturnType<typeof createRemoteComponentRenderer>>([
${entries}
['${CUSTOM_ELEMENT_NAMES.FRAGMENT}', RemoteFragmentRenderer],
]);`;
};
export const generateHostRegistry = (
project: Project,
components: ComponentSchema[],
eventToReactMapping: Record<string, string>,
): SourceFile => {
const sourceFile = project.createSourceFile(
'host-component-registry.ts',
'',
{ overwrite: true },
);
sourceFile.addImportDeclaration({
moduleSpecifier: 'react',
defaultImport: 'React',
});
sourceFile.addImportDeclaration({
moduleSpecifier: '@remote-dom/react/host',
namedImports: ['RemoteFragmentRenderer', 'createRemoteComponentRenderer'],
});
addStatement(sourceFile, generateRuntimeUtilities(eventToReactMapping));
for (const component of components) {
addStatement(sourceFile, generateWrapperComponent(component));
}
addStatement(sourceFile, generateRegistryMap(components));
addFileHeader(sourceFile);
return sourceFile;
};
@@ -1,6 +0,0 @@
export * from './constants';
export { generateHostRegistry } from './host-registry.generator';
export { generateRemoteComponents } from './remote-components.generator';
export { generateRemoteElements } from './remote-elements.generator';
export * from './schemas';
export * from './utils';
@@ -1,64 +0,0 @@
import type { Project, SourceFile } from 'ts-morph';
import { type ComponentSchema } from './schemas';
import { addExportedConst, addFileHeader, eventToReactProp } from './utils';
const generateComponentDefinition = (
sourceFile: SourceFile,
component: ComponentSchema,
): void => {
const hasEvents = component.events.length > 0;
const componentExportName = component.tagName;
let initializer: string;
if (hasEvents) {
const eventProps = component.events
.map((event) => {
const propName = eventToReactProp(event);
return ` ${propName}: { event: '${event}' },`;
})
.join('\n');
initializer = `createRemoteComponent('${component.customElementName}', ${component.name}Element, {
eventProps: {
${eventProps}
},
})`;
} else {
initializer = `createRemoteComponent('${component.customElementName}', ${component.name}Element)`;
}
addExportedConst(sourceFile, componentExportName, initializer);
};
export const generateRemoteComponents = (
project: Project,
components: ComponentSchema[],
): SourceFile => {
const sourceFile = project.createSourceFile('remote-components.ts', '', {
overwrite: true,
});
sourceFile.addImportDeclaration({
moduleSpecifier: '@remote-dom/react',
namedImports: ['createRemoteComponent'],
});
const elementImports = components.map(
(component) => `${component.name}Element`,
);
sourceFile.addImportDeclaration({
moduleSpecifier: './remote-elements',
namedImports: elementImports,
});
for (const component of components) {
generateComponentDefinition(sourceFile, component);
}
addFileHeader(sourceFile);
return sourceFile;
};
@@ -1,389 +0,0 @@
import {
type Project,
type SourceFile,
VariableDeclarationKind,
type WriterFunction,
} from 'ts-morph';
import {
CUSTOM_ELEMENT_NAMES,
INTERNAL_ELEMENT_CLASSES,
TYPE_NAMES,
} from './constants';
import { type ComponentSchema, type PropertySchema } from './schemas';
import {
addFileHeader,
generatePropertiesConfig,
schemaTypeToConstructor,
schemaTypeToTs,
} from './utils';
type ElementGenerationOptions = {
useSharedEvents: boolean;
useSharedPropertiesConfig: boolean;
};
type ComponentWithSpecificProps = {
component: ComponentSchema;
specificProperties: Record<string, PropertySchema>;
};
const getElementSpecificProperties = (
component: ComponentSchema,
commonPropertyNames: Set<string>,
): Record<string, PropertySchema> => {
const specific: Record<string, PropertySchema> = {};
for (const [name, schema] of Object.entries(component.properties)) {
if (!commonPropertyNames.has(name)) {
specific[name] = schema;
}
}
return specific;
};
const generatePropertyEntries = (
properties: Record<string, PropertySchema>,
): string[] => {
return Object.entries(properties).map(([name, schema]) => {
const tsType = schemaTypeToTs(schema.type);
const optional = schema.optional ? '?' : '';
return `'${name}'${optional}: ${tsType}`;
});
};
const generateCommonPropertiesType = (
sourceFile: SourceFile,
commonProperties: Record<string, PropertySchema>,
): void => {
const entries = generatePropertyEntries(commonProperties);
sourceFile.addTypeAlias({
isExported: true,
name: TYPE_NAMES.COMMON_PROPERTIES,
type: (writer) => {
writer.block(() => {
for (const entry of entries) {
writer.writeLine(`${entry};`);
}
});
},
});
};
const generateCommonEventsType = (
sourceFile: SourceFile,
events: readonly string[],
): void => {
if (events.length === 0) {
return;
}
sourceFile.addTypeAlias({
isExported: true,
name: TYPE_NAMES.COMMON_EVENTS,
type: (writer) => {
writer.block(() => {
for (const event of events) {
writer.writeLine(`${event}(event: RemoteEvent): void;`);
}
});
},
});
sourceFile.addVariableStatement({
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: TYPE_NAMES.COMMON_EVENTS_ARRAY,
initializer: (writer) => {
writer.write('[');
writer.newLine();
writer.indent(() => {
for (const event of events) {
writer.writeLine(`'${event}',`);
}
});
writer.write('] as const');
},
},
],
});
};
const generateCommonPropertiesConfig = (
sourceFile: SourceFile,
commonProperties: Record<string, PropertySchema>,
): void => {
sourceFile.addVariableStatement({
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: TYPE_NAMES.COMMON_PROPERTIES_CONFIG,
initializer: (writer) => {
writer.block(() => {
for (const [name, schema] of Object.entries(commonProperties)) {
const constructorType = schemaTypeToConstructor(schema.type);
writer.writeLine(`'${name}': { type: ${constructorType} },`);
}
});
},
},
],
});
};
const generateElementPropertyType = (
sourceFile: SourceFile,
component: ComponentSchema,
specificProperties: Record<string, PropertySchema>,
): void => {
const hasSpecificProps = Object.keys(specificProperties).length > 0;
if (hasSpecificProps) {
const entries = generatePropertyEntries(specificProperties);
sourceFile.addTypeAlias({
isExported: true,
name: `${component.name}Properties`,
type: (writer) => {
writer.write(`${TYPE_NAMES.COMMON_PROPERTIES} & `);
writer.block(() => {
for (const entry of entries) {
writer.writeLine(`${entry};`);
}
});
},
});
}
};
const createPropertiesConfigWriter = (
properties: Record<string, PropertySchema>,
): WriterFunction => {
return (writer) => {
writer.write(generatePropertiesConfig(properties));
};
};
const generateElementDefinition = (
sourceFile: SourceFile,
component: ComponentSchema,
specificProperties: Record<string, PropertySchema>,
options: ElementGenerationOptions,
): void => {
const { useSharedEvents, useSharedPropertiesConfig } = options;
const hasEvents = component.events.length > 0;
const hasSpecificProps = Object.keys(specificProperties).length > 0;
const hasProps = Object.keys(component.properties).length > 0;
const propsType = hasSpecificProps
? `${component.name}Properties`
: hasProps
? TYPE_NAMES.COMMON_PROPERTIES
: TYPE_NAMES.EMPTY_RECORD;
const eventsType = hasEvents
? useSharedEvents
? TYPE_NAMES.COMMON_EVENTS
: `{ ${component.events.map((event) => `${event}(event: RemoteEvent): void`).join('; ')} }`
: TYPE_NAMES.EMPTY_RECORD;
sourceFile.addVariableStatement({
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: `${component.name}Element`,
initializer: (writer) => {
writer.write('createRemoteElement<');
writer.newLine();
writer.indent(() => {
writer.writeLine(`${propsType},`);
writer.writeLine('Record<string, never>,');
writer.writeLine('Record<string, never>,');
writer.write(eventsType);
});
writer.newLine();
writer.write('>');
const hasConfig = hasProps || hasEvents;
if (!hasConfig) {
writer.write('({})');
return;
}
writer.write('(');
writer.block(() => {
if (hasProps) {
if (hasSpecificProps) {
writer.write('properties: ');
writer.block(() => {
writer.writeLine(
`...${TYPE_NAMES.COMMON_PROPERTIES_CONFIG},`,
);
for (const [name, schema] of Object.entries(
specificProperties,
)) {
const constructorType = schemaTypeToConstructor(
schema.type,
);
writer.writeLine(
`'${name}': { type: ${constructorType} },`,
);
}
});
writer.write(',');
writer.newLine();
} else if (useSharedPropertiesConfig) {
writer.write(
`properties: ${TYPE_NAMES.COMMON_PROPERTIES_CONFIG},`,
);
writer.newLine();
} else {
writer.write('properties: ');
createPropertiesConfigWriter(component.properties)(writer);
writer.write(',');
writer.newLine();
}
}
if (hasEvents) {
writer.write(
useSharedEvents
? `events: [...${TYPE_NAMES.COMMON_EVENTS_ARRAY}],`
: `events: [${component.events.map((event) => `'${event}'`).join(', ')}],`,
);
writer.newLine();
}
});
writer.write(')');
},
},
],
});
};
const generateCustomElementRegistrations = (
sourceFile: SourceFile,
components: ComponentSchema[],
): void => {
for (const component of components) {
sourceFile.addStatements(
`customElements.define('${component.customElementName}', ${component.name}Element);`,
);
}
sourceFile.addStatements(
`customElements.define('${CUSTOM_ELEMENT_NAMES.ROOT}', ${INTERNAL_ELEMENT_CLASSES.ROOT});`,
);
sourceFile.addStatements(
`customElements.define('${CUSTOM_ELEMENT_NAMES.FRAGMENT}', ${INTERNAL_ELEMENT_CLASSES.FRAGMENT});`,
);
};
const generateTagNameMapDeclaration = (
sourceFile: SourceFile,
components: ComponentSchema[],
): void => {
sourceFile.addStatements((writer) => {
writer.writeLine('declare global {');
writer.indent(() => {
writer.writeLine('interface HTMLElementTagNameMap {');
writer.indent(() => {
for (const component of components) {
writer.writeLine(
`'${component.customElementName}': InstanceType<typeof ${component.name}Element>;`,
);
}
writer.writeLine(
`'${CUSTOM_ELEMENT_NAMES.ROOT}': InstanceType<typeof ${INTERNAL_ELEMENT_CLASSES.ROOT}>;`,
);
writer.writeLine(
`'${CUSTOM_ELEMENT_NAMES.FRAGMENT}': InstanceType<typeof ${INTERNAL_ELEMENT_CLASSES.FRAGMENT}>;`,
);
});
writer.writeLine('}');
});
writer.writeLine('}');
});
};
const prepareComponentsWithSpecificProps = (
components: ComponentSchema[],
commonPropertyNames: Set<string>,
): ComponentWithSpecificProps[] => {
return components.map((component) => ({
component,
specificProperties: getElementSpecificProperties(
component,
commonPropertyNames,
),
}));
};
export const generateRemoteElements = (
project: Project,
components: ComponentSchema[],
commonProperties: Record<string, PropertySchema>,
commonEvents: readonly string[] = [],
): SourceFile => {
const sourceFile = project.createSourceFile('remote-elements.ts', '', {
overwrite: true,
});
const useSharedEvents = commonEvents.length > 0;
const useSharedPropertiesConfig = Object.keys(commonProperties).length > 0;
const options: ElementGenerationOptions = {
useSharedEvents,
useSharedPropertiesConfig,
};
sourceFile.addImportDeclaration({
moduleSpecifier: '@remote-dom/core/elements',
namedImports: [
'createRemoteElement',
INTERNAL_ELEMENT_CLASSES.ROOT,
INTERNAL_ELEMENT_CLASSES.FRAGMENT,
{ name: 'RemoteEvent', isTypeOnly: true },
],
});
const commonPropertyNames = new Set(Object.keys(commonProperties));
const componentsWithProps = prepareComponentsWithSpecificProps(
components,
commonPropertyNames,
);
generateCommonPropertiesType(sourceFile, commonProperties);
if (useSharedEvents) {
generateCommonEventsType(sourceFile, commonEvents);
}
if (useSharedPropertiesConfig) {
generateCommonPropertiesConfig(sourceFile, commonProperties);
}
for (const { component, specificProperties } of componentsWithProps) {
generateElementPropertyType(sourceFile, component, specificProperties);
generateElementDefinition(
sourceFile,
component,
specificProperties,
options,
);
}
generateCustomElementRegistrations(sourceFile, components);
sourceFile.addExportDeclaration({
namedExports: [
INTERNAL_ELEMENT_CLASSES.ROOT,
INTERNAL_ELEMENT_CLASSES.FRAGMENT,
],
});
generateTagNameMapDeclaration(sourceFile, components);
addFileHeader(sourceFile);
return sourceFile;
};
@@ -1,30 +0,0 @@
import { z } from 'zod';
export const PropertySchemaZ = z.object({
type: z.enum(['string', 'number', 'boolean']),
optional: z.boolean(),
});
export const HtmlElementConfigZ = z.object({
tag: z.string().regex(/^html-[a-z0-9]+$/, 'Tag must start with "html-"'),
name: z
.string()
.regex(/^Html[A-Z]/, 'Name must be PascalCase starting with Html'),
properties: z.record(z.string(), PropertySchemaZ),
});
export const HtmlElementConfigArrayZ = z.array(HtmlElementConfigZ);
export const ComponentSchemaZ = z.object({
name: z.string().min(1),
tagName: z.string().min(1),
customElementName: z.string().min(1),
properties: z.record(z.string(), PropertySchemaZ),
events: z.array(z.string()).readonly(),
isHtmlElement: z.boolean(),
htmlTag: z.string().min(1),
});
export type PropertySchema = z.infer<typeof PropertySchemaZ>;
export type HtmlElementConfig = z.infer<typeof HtmlElementConfigZ>;
export type ComponentSchema = z.infer<typeof ComponentSchemaZ>;
@@ -1,13 +0,0 @@
import { type SourceFile, VariableDeclarationKind } from 'ts-morph';
export const addExportedConst = (
sourceFile: SourceFile,
name: string,
initializer: string,
): void => {
sourceFile.addVariableStatement({
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [{ name, initializer }],
});
};
@@ -1,13 +0,0 @@
import { type SourceFile } from 'ts-morph';
export const addExportedType = (
sourceFile: SourceFile,
name: string,
type: string,
): void => {
sourceFile.addTypeAlias({
isExported: true,
name,
type,
});
};
@@ -1,7 +0,0 @@
import { type SourceFile } from 'ts-morph';
import { GENERATED_FILE_HEADER } from './generated-file-header';
export const addFileHeader = (sourceFile: SourceFile): void => {
sourceFile.insertText(0, GENERATED_FILE_HEADER + '\n\n');
};
@@ -1,8 +0,0 @@
import { type SourceFile } from 'ts-morph';
export const addStatement = (
sourceFile: SourceFile,
statement: string,
): void => {
sourceFile.addStatements(statement);
};
@@ -1,3 +0,0 @@
export const eventToReactProp = (eventName: string): string => {
return `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`;
};
@@ -1,3 +0,0 @@
export const extractHtmlTag = (tag: string): string => {
return tag.startsWith('html-') ? tag.slice(5) : tag;
};
@@ -1,20 +0,0 @@
import { type PropertySchema } from '../schemas';
import { schemaTypeToConstructor } from './schema-type-to-constructor';
export const generatePropertiesConfig = (
properties: Record<string, PropertySchema>,
): string => {
const entries = Object.entries(properties);
if (entries.length === 0) {
return '{}';
}
const props = entries
.map(([name, schema]) => {
const constructorType = schemaTypeToConstructor(schema.type);
return `'${name}': { type: ${constructorType} }`;
})
.join(', ');
return `{ ${props} }`;
};
@@ -1,21 +0,0 @@
import { type PropertySchema } from '../schemas';
import { schemaTypeToTs } from './schema-type-to-ts';
export const generatePropertiesType = (
properties: Record<string, PropertySchema>,
): string => {
const entries = Object.entries(properties);
if (entries.length === 0) {
return 'Record<string, never>';
}
const props = entries
.map(([name, schema]) => {
const tsType = schemaTypeToTs(schema.type);
const optional = schema.optional ? '?' : '';
return `'${name}'${optional}: ${tsType}`;
})
.join('; ');
return `{ ${props} }`;
};
@@ -1,8 +0,0 @@
export const GENERATED_FILE_HEADER = `/*
* _____ _
*|_ _|_ _____ _ __ | |_ _ _
* | | \\ \\ /\\ / / _ \\ '_ \\| __| | | | Auto-generated file
* | | \\ V V / __/ | | | |_| |_| | Any edits to this will be overridden
* |_| \\_/\\_/ \\___|_| |_|\\__|\\__, |
* |___/
*/`;
@@ -1,11 +0,0 @@
export { addExportedConst } from './add-exported-const';
export { addExportedType } from './add-exported-type';
export { addFileHeader } from './add-file-header';
export { addStatement } from './add-statement';
export { eventToReactProp } from './event-to-react-prop';
export { extractHtmlTag } from './extract-html-tag';
export { GENERATED_FILE_HEADER } from './generated-file-header';
export { generatePropertiesConfig } from './generate-properties-config';
export { generatePropertiesType } from './generate-properties-type';
export { schemaTypeToConstructor } from './schema-type-to-constructor';
export { schemaTypeToTs } from './schema-type-to-ts';
@@ -1,10 +0,0 @@
import { type PropertySchema } from '../schemas';
const SCHEMA_TYPE_TO_CONSTRUCTOR: Record<PropertySchema['type'], string> = {
boolean: 'Boolean',
number: 'Number',
string: 'String',
};
export const schemaTypeToConstructor = (type: PropertySchema['type']): string =>
SCHEMA_TYPE_TO_CONSTRUCTOR[type];
@@ -1,10 +0,0 @@
import { type PropertySchema } from '../schemas';
const SCHEMA_TYPE_TO_TS: Record<PropertySchema['type'], string> = {
boolean: 'boolean',
number: 'number',
string: 'string',
};
export const schemaTypeToTs = (type: PropertySchema['type']): string =>
SCHEMA_TYPE_TO_TS[type];