From 945724e016d05744e73959c7dca80ff60efc9aba Mon Sep 17 00:00:00 2001 From: martmull Date: Fri, 10 Jul 2026 15:15:26 +0200 Subject: [PATCH] accepts any valid expression, not just an identifier (#22778) Fixing and issue with front component definition look at the unit test to see the actual fix --- ...e-front-component-to-direct-export.spec.ts | 87 +++++++++++++++++++ ...define-front-component-to-direct-export.ts | 47 +++++----- 2 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/__tests__/unwrap-define-front-component-to-direct-export.spec.ts diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/__tests__/unwrap-define-front-component-to-direct-export.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/__tests__/unwrap-define-front-component-to-direct-export.spec.ts new file mode 100644 index 0000000000..304466c2ec --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/__tests__/unwrap-define-front-component-to-direct-export.spec.ts @@ -0,0 +1,87 @@ +import { unwrapDefineFrontComponentToDirectExport } from '@/cli/utilities/build/common/front-component-build/utils/unwrap-define-front-component-to-direct-export'; + +const INLINE_EXPRESSION_COMPONENT_SOURCE = `import { defineFrontComponent } from 'twenty-sdk/define'; + +import { SUMMARIZE_COMPANY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers'; +import { createSummarizeRecordEffect } from 'src/front-components/utils/create-summarize-record-effect'; +import { SUMMARIZE_TARGETS } from 'src/front-components/utils/summarize-target'; + +const SummarizeRecordEffect = createSummarizeRecordEffect(SUMMARIZE_TARGETS.company) + +export default defineFrontComponent({ + universalIdentifier: SUMMARIZE_COMPANY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: 'Summarize Company', + description: 'Asks AI to summarize the selected company', + isHeadless: true, + component: createSummarizeRecordEffect(SUMMARIZE_TARGETS.company), +}); +`; + +const IDENTIFIER_COMPONENT_SOURCE = `import { defineFrontComponent } from 'twenty-sdk/define'; + +import { SUMMARIZE_COMPANY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers'; +import { createSummarizeRecordEffect } from 'src/front-components/utils/create-summarize-record-effect'; +import { SUMMARIZE_TARGETS } from 'src/front-components/utils/summarize-target'; + +const SummarizeRecordEffect = createSummarizeRecordEffect(SUMMARIZE_TARGETS.company) + +export default defineFrontComponent({ + universalIdentifier: SUMMARIZE_COMPANY_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: 'Summarize Company', + description: 'Asks AI to summarize the selected company', + isHeadless: true, + component: SummarizeRecordEffect, +}); +`; + +describe('unwrapDefineFrontComponentToDirectExport', () => { + it('renders the component when component is an inline factory call', () => { + const output = unwrapDefineFrontComponentToDirectExport( + INLINE_EXPRESSION_COMPONENT_SOURCE, + ); + + expect(output).toContain( + 'component: createSummarizeRecordEffect(SUMMARIZE_TARGETS.company),', + ); + expect(output).toContain( + '__frontComponentJsx(__frontComponentDefinition.component, {})', + ); + expect(output).not.toContain('defineFrontComponent'); + }); + + it('renders the component when component is a bare identifier', () => { + const output = unwrapDefineFrontComponentToDirectExport( + IDENTIFIER_COMPONENT_SOURCE, + ); + + expect(output).toContain('component: SummarizeRecordEffect,'); + expect(output).toContain( + '__frontComponentJsx(__frontComponentDefinition.component, {})', + ); + expect(output).not.toContain('defineFrontComponent'); + }); + + it('produces an equivalent renderer for the inline and identifier forms', () => { + const inlineOutput = unwrapDefineFrontComponentToDirectExport( + INLINE_EXPRESSION_COMPONENT_SOURCE, + ); + const identifierOutput = unwrapDefineFrontComponentToDirectExport( + IDENTIFIER_COMPONENT_SOURCE, + ); + + const renderer = + '\nexport default function __renderFrontComponent(__container) { __createRoot(__container).render(__frontComponentJsx(__frontComponentDefinition.component, {})); }\n'; + + expect(inlineOutput).toContain(renderer); + expect(identifierOutput).toContain(renderer); + }); + + it('leaves a source without a defineFrontComponent default export untouched', () => { + const source = `import { useEffect } from 'react'; + +export const createSummarizeRecordEffect = () => () => null; +`; + + expect(unwrapDefineFrontComponentToDirectExport(source)).toBe(source); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/unwrap-define-front-component-to-direct-export.ts b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/unwrap-define-front-component-to-direct-export.ts index b197c6237d..dd1ad67c95 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/unwrap-define-front-component-to-direct-export.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/front-component-build/utils/unwrap-define-front-component-to-direct-export.ts @@ -1,8 +1,13 @@ const DEFINE_FRONT_COMPONENT_IMPORT_PATTERN = /import\s*\{\s*defineFrontComponent\s*\}\s*from\s*['"][^'"]+['"];?\n?/g; -const DEFINE_FRONT_COMPONENT_EXPORT_PATTERN = - /export\s+default\s+defineFrontComponent\s*\(\s*\{[\s\S]*?component\s*:\s*(\w+)[\s\S]*?\}\s*\)\s*;?/s; +const DEFINE_FRONT_COMPONENT_EXPORT_OPENING_PATTERN = + /export\s+default\s+defineFrontComponent\s*\(/; + +const IDENTIFIER_COMPONENT_VALUE_PATTERN = + /component\s*:\s*([A-Za-z_$][\w$]*)\s*[,}]/; + +const FRONT_COMPONENT_DEFINITION_NAME = '__frontComponentDefinition'; export const unwrapDefineFrontComponentToDirectExport = ( sourceCode: string, @@ -12,32 +17,32 @@ export const unwrapDefineFrontComponentToDirectExport = ( '', ); - const defineFrontComponentMatch = transformedSource.match( - DEFINE_FRONT_COMPONENT_EXPORT_PATTERN, + if (!DEFINE_FRONT_COMPONENT_EXPORT_OPENING_PATTERN.test(transformedSource)) { + return transformedSource; + } + + transformedSource = transformedSource.replace( + DEFINE_FRONT_COMPONENT_EXPORT_OPENING_PATTERN, + `const ${FRONT_COMPONENT_DEFINITION_NAME} = (`, ); - if (defineFrontComponentMatch) { - const wrappedComponentName = defineFrontComponentMatch[1]; + const identifierComponentMatch = transformedSource.match( + IDENTIFIER_COMPONENT_VALUE_PATTERN, + ); - const exportedComponentDeclarationPattern = new RegExp( - `export\\s+(const|function)\\s+${wrappedComponentName}\\b`, - ); + if (identifierComponentMatch) { + const wrappedComponentName = identifierComponentMatch[1]; transformedSource = transformedSource.replace( - exportedComponentDeclarationPattern, + new RegExp(`export\\s+(const|function)\\s+${wrappedComponentName}\\b`), `$1 ${wrappedComponentName}`, ); - - transformedSource = - `import { createRoot as __createRoot } from 'react-dom/client';\n` + - `import { jsx as __frontComponentJsx } from 'react/jsx-runtime';\n` + - transformedSource; - - transformedSource = transformedSource.replace( - DEFINE_FRONT_COMPONENT_EXPORT_PATTERN, - `export default function __renderFrontComponent(__container) { __createRoot(__container).render(__frontComponentJsx(${wrappedComponentName}, {})); }`, - ); } - return transformedSource; + return ( + `import { createRoot as __createRoot } from 'react-dom/client';\n` + + `import { jsx as __frontComponentJsx } from 'react/jsx-runtime';\n` + + transformedSource + + `\nexport default function __renderFrontComponent(__container) { __createRoot(__container).render(__frontComponentJsx(${FRONT_COMPONENT_DEFINITION_NAME}.component, {})); }\n` + ); };