Compare commits

...

1 Commits

Author SHA1 Message Date
bosiraphael 6e22709e28 wip 2026-02-18 14:42:10 +01:00
17 changed files with 418 additions and 18 deletions
+2
View File
@@ -63,6 +63,7 @@
"commander": "^12.0.0",
"dotenv": "^16.4.0",
"esbuild": "^0.25.0",
"esbuild-svelte": "^0.9.4",
"fast-glob": "^3.3.0",
"fs-extra": "^11.2.0",
"graphql": "^16.8.1",
@@ -95,6 +96,7 @@
"@vitest/browser-playwright": "^4.0.18",
"playwright": "^1.56.1",
"storybook": "^10.1.11",
"svelte": "^4.2.19",
"ts-morph": "^25.0.0",
"tsx": "^4.7.0",
"twenty-shared": "workspace:*",
@@ -54,9 +54,21 @@ const twentySharedAliases = Object.fromEntries(
]),
);
const svelteRoot = path.join(rootNodeModules, 'svelte');
const storyAlias = {
react: path.join(rootNodeModules, 'react'),
'react-dom': path.join(rootNodeModules, 'react-dom'),
vue: path.join(rootNodeModules, 'vue'),
svelte: svelteRoot,
'svelte/internal': path.join(
svelteRoot,
'src/runtime/internal/index.js',
),
'svelte/internal/disclose-version': path.join(
svelteRoot,
'src/runtime/internal/disclose-version/index.js',
),
'@/sdk': sdkIndividualIndex,
'twenty-sdk/ui': twentyUiIndividualIndex,
...twentySharedAliases,
@@ -74,6 +86,8 @@ const STORY_COMPONENTS = [
'mui-example.front-component',
'twenty-ui-example.front-component',
'sdk-context-example.front-component',
'vue-example.front-component',
'svelte-example.front-component',
];
const resolveEntryPoints = (): Record<string, string> => {
@@ -1,4 +1,5 @@
import type * as esbuild from 'esbuild';
import sveltePlugin from 'esbuild-svelte';
import { createJsxRuntimeRemoteWrapperPlugin } from '../jsx-runtime-remote-wrapper-plugin';
import { jsxTransformToRemoteDomWorkerFormatPlugin } from '../jsx-transform-to-remote-dom-worker-format-plugin';
@@ -12,6 +13,7 @@ type GetFrontComponentBuildPluginsOptions = {
export const getFrontComponentBuildPlugins = (
options?: GetFrontComponentBuildPluginsOptions,
): esbuild.Plugin[] => [
sveltePlugin(),
createJsxRuntimeRemoteWrapperPlugin(
options?.usePreact ? { usePreact: true } : undefined,
),
@@ -1,9 +1,34 @@
import { type FrontComponentFramework } from 'twenty-shared/application';
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*\{[^}]*component\s*:\s*(\w+)[^}]*\}\s*\)\s*;?/s;
const FRAMEWORK_PATTERN = /framework\s*:\s*['"](\w+)['"]/;
const extractFramework = (sourceCode: string): FrontComponentFramework => {
const match = sourceCode.match(FRAMEWORK_PATTERN);
return (match?.[1] as FrontComponentFramework) ?? 'react';
};
const REACT_MOUNT_IMPORTS =
`import { createRoot as __createRoot } from 'react-dom/client';\n` +
`import { jsx as __frontComponentJsx } from 'react/jsx-runtime';\n`;
const generateRenderExport = (
framework: FrontComponentFramework,
componentName: string,
): string => {
if (framework === 'react') {
return `export default function __renderFrontComponent(__container) { __createRoot(__container).render(__frontComponentJsx(${componentName}, {})); }`;
}
return `export default function __renderFrontComponent(__container) { ${componentName}(__container); }`;
};
export const unwrapDefineFrontComponentToDirectExport = (
sourceCode: string,
): string => {
@@ -18,6 +43,7 @@ export const unwrapDefineFrontComponentToDirectExport = (
if (defineFrontComponentMatch) {
const wrappedComponentName = defineFrontComponentMatch[1];
const framework = extractFramework(sourceCode);
const exportedComponentDeclarationPattern = new RegExp(
`export\\s+(const|function)\\s+${wrappedComponentName}\\b`,
@@ -28,14 +54,13 @@ export const unwrapDefineFrontComponentToDirectExport = (
`$1 ${wrappedComponentName}`,
);
transformedSource =
`import { createRoot as __createRoot } from 'react-dom/client';\n` +
`import { jsx as __frontComponentJsx } from 'react/jsx-runtime';\n` +
transformedSource;
if (framework === 'react') {
transformedSource = REACT_MOUNT_IMPORTS + transformedSource;
}
transformedSource = transformedSource.replace(
DEFINE_FRONT_COMPONENT_EXPORT_PATTERN,
`export default function __renderFrontComponent(__container) { __createRoot(__container).render(__frontComponentJsx(${wrappedComponentName}, {})); }`,
generateRenderExport(framework, wrappedComponentName),
);
}
@@ -203,6 +203,7 @@ export const buildManifest = async (
const config: FrontComponentManifest = {
...rest,
framework: rest.framework ?? 'react',
componentName: component.name,
sourceComponentPath: relativeFilePath,
builtComponentPath: relativeFilePath.replace(/\.tsx?$/, '.mjs'),
@@ -0,0 +1,72 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, fn, userEvent, within } from 'storybook/test';
import { FrontComponentRenderer } from '../host/components/FrontComponentRenderer';
import { getBuiltStoryComponentPathForRender } from './utils/getBuiltStoryComponentPathForRender';
const errorHandler = fn();
const meta: Meta<typeof FrontComponentRenderer> = {
title: 'FrontComponent/Other Frameworks',
component: FrontComponentRenderer,
parameters: {
layout: 'centered',
},
args: {
onError: errorHandler,
applicationAccessToken: 'fake-token',
},
beforeEach: () => {
errorHandler.mockClear();
},
};
export default meta;
type Story = StoryObj<typeof FrontComponentRenderer>;
const createComponentStory = (
name: string,
options?: { runtime?: 'preact'; play?: Story['play'] },
): Story => ({
args: {
componentUrl: getBuiltStoryComponentPathForRender(
`${name}.front-component`,
options?.runtime,
),
},
...(options?.play ? { play: options.play } : {}),
});
const createCounterTest =
(testIdPrefix: string): Story['play'] =>
async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByTestId(
`${testIdPrefix}-component`,
{},
{ timeout: 30000 },
);
expect(await canvas.findByText('Count: 0')).toBeVisible();
const button = await canvas.findByTestId(`${testIdPrefix}-button`);
await userEvent.click(button);
expect(await canvas.findByText('Count: 1')).toBeVisible();
await userEvent.click(button);
expect(await canvas.findByText('Count: 2')).toBeVisible();
};
const vueTest = createCounterTest('vue');
export const Vue: Story = createComponentStory('vue-example', {
play: vueTest,
});
const svelteTest = createCounterTest('svelte');
export const Svelte: Story = createComponentStory('svelte-example', {
play: svelteTest,
});
@@ -0,0 +1,32 @@
<script>
let count = 0;
</script>
<div
data-testid="svelte-component"
style="padding: 24px; background-color: #fff7ed; border: 2px solid #fb923c; border-radius: 12px; font-family: system-ui, sans-serif;"
>
<h2
style="color: #9a3412; font-weight: 700; font-size: 18px; margin-bottom: 12px;"
>
Svelte Component
</h2>
<span
style="display: inline-block; padding: 2px 8px; background-color: #fb923c; color: white; border-radius: 4px; font-size: 11px; font-weight: 600; margin-bottom: 16px;"
>
svelte
</span>
<p
data-testid="svelte-count"
style="font-size: 28px; font-weight: 800; color: #c2410c; margin-top: 16px; margin-bottom: 16px;"
>
Count: {count}
</p>
<button
data-testid="svelte-button"
on:click={() => count++}
style="padding: 10px 20px; background-color: #f97316; color: white; border: none; border-radius: 6px; font-weight: 600; cursor: pointer;"
>
Increment
</button>
</div>
@@ -0,0 +1,18 @@
import { defineFrontComponent } from '@/sdk';
// @ts-expect-error -- esbuild-svelte handles .svelte imports at build time
import SvelteCounter from './svelte-counter.svelte';
const SvelteExampleComponent = (container: HTMLElement) => {
const component = new SvelteCounter({ target: container });
return () => component.$destroy();
};
export default defineFrontComponent({
universalIdentifier: 'test-svel-00000000-0000-0000-0000-000000000011',
name: 'svelte-example',
description: 'A Svelte framework front component example',
framework: 'svelte',
component: SvelteExampleComponent,
});
@@ -0,0 +1,101 @@
import { createApp, defineComponent, h, ref } from 'vue';
import { defineFrontComponent } from '@/sdk';
const VueCounter = defineComponent({
setup: () => {
const count = ref(0);
return () =>
h(
'div',
{
'data-testid': 'vue-component',
style: {
padding: '24px',
backgroundColor: '#f0fdf4',
border: '2px solid #4ade80',
borderRadius: '12px',
fontFamily: 'system-ui, sans-serif',
},
},
[
h(
'h2',
{
style: {
color: '#166534',
fontWeight: '700',
fontSize: '18px',
marginBottom: '12px',
},
},
'Vue Component',
),
h(
'span',
{
style: {
display: 'inline-block',
padding: '2px 8px',
backgroundColor: '#4ade80',
color: 'white',
borderRadius: '4px',
fontSize: '11px',
fontWeight: '600',
marginBottom: '16px',
},
},
'vue',
),
h('br'),
h(
'p',
{
'data-testid': 'vue-count',
style: {
fontSize: '28px',
fontWeight: '800',
color: '#15803d',
marginBottom: '16px',
},
},
`Count: ${count.value}`,
),
h(
'button',
{
'data-testid': 'vue-button',
onClick: () => count.value++,
style: {
padding: '10px 20px',
backgroundColor: '#22c55e',
color: 'white',
border: 'none',
borderRadius: '6px',
fontWeight: '600',
cursor: 'pointer',
},
},
'Increment',
),
],
);
},
});
const VueExampleComponent = (container: HTMLElement) => {
const app = createApp(VueCounter);
app.mount(container);
return () => app.unmount();
};
export default defineFrontComponent({
universalIdentifier: 'test-vue0-00000000-0000-0000-0000-000000000010',
name: 'vue-example',
description: 'A Vue framework front component example',
framework: 'vue',
component: VueExampleComponent,
});
@@ -0,0 +1,34 @@
import { HTML_TAG_TO_CUSTOM_ELEMENT_TAG } from '@/sdk/front-component-api/constants/HtmlTagToRemoteComponent';
// Frameworks like Vue and Svelte call document.createElement /
// document.createElementNS directly instead of going through React's JSX
// runtime. This patch remaps standard HTML tag names (e.g. "div") to their
// remote-dom custom element equivalents (e.g. "html-div") so the host
// component registry can resolve them.
export const patchDocumentCreateElementForRemoteDom = (): void => {
const originalCreateElement = document.createElement.bind(document);
document.createElement = ((
tagName: string,
options?: ElementCreationOptions,
): HTMLElement => {
const remappedTag =
HTML_TAG_TO_CUSTOM_ELEMENT_TAG[tagName.toLowerCase()] ?? tagName;
return originalCreateElement(remappedTag, options);
}) as typeof document.createElement;
const originalCreateElementNS = document.createElementNS.bind(document);
document.createElementNS = ((
namespaceURI: string | null,
qualifiedName: string,
options?: ElementCreationOptions,
): Element => {
const remappedTag =
HTML_TAG_TO_CUSTOM_ELEMENT_TAG[qualifiedName.toLowerCase()] ??
qualifiedName;
return originalCreateElementNS(namespaceURI, remappedTag, options);
}) as typeof document.createElementNS;
};
@@ -14,6 +14,7 @@ import { isDefined } from 'twenty-shared/utils';
import { installStyleBridge } from '@/front-component-renderer/polyfills/installStyleBridge';
import { installStylePropertyOnRemoteElements } from '@/front-component-renderer/remote/utils/installStylePropertyOnRemoteElements';
import { patchDocumentCreateElementForRemoteDom } from '@/front-component-renderer/remote/utils/patchDocumentCreateElementForRemoteDom';
import { patchRemoteElementSetAttribute } from '@/front-component-renderer/remote/utils/patchRemoteElementSetAttribute';
import { HTML_TAG_TO_CUSTOM_ELEMENT_TAG } from '@/sdk/front-component-api/constants/HtmlTagToRemoteComponent';
import { setFrontComponentExecutionContext } from '@/sdk/front-component-api/context/frontComponentContext';
@@ -28,6 +29,7 @@ import { setWorkerEnv } from './utils/setWorkerEnv';
installStylePropertyOnRemoteElements();
patchRemoteElementSetAttribute();
patchDocumentCreateElementForRemoteDom();
exposeGlobals({
__HTML_TAG_TO_CUSTOM_ELEMENT_TAG__: HTML_TAG_TO_CUSTOM_ELEMENT_TAG,
@@ -2,6 +2,8 @@ import { createValidationResult } from '@/sdk';
import type { DefineEntity } from '@/sdk/common/types/define-entity.type';
import { type FrontComponentConfig } from '@/sdk/front-component-config';
const VALID_FRAMEWORKS = ['react', 'vue', 'svelte', 'angular'] as const;
export const defineFrontComponent: DefineEntity<FrontComponentConfig> = (
config,
) => {
@@ -15,8 +17,25 @@ export const defineFrontComponent: DefineEntity<FrontComponentConfig> = (
errors.push('Front component must have a component');
}
if (typeof config.component !== 'function') {
errors.push('Front component component must be a React component');
const framework = config.framework ?? 'react';
if (!VALID_FRAMEWORKS.includes(framework)) {
errors.push(
`Front component framework must be one of: ${VALID_FRAMEWORKS.join(', ')}`,
);
}
if (framework === 'react' && typeof config.component !== 'function') {
errors.push('React front component must be a function component');
}
if (
framework !== 'react' &&
typeof config.component !== 'function'
) {
errors.push(
'Non-React front component must be a render function (container: HTMLElement) => void',
);
}
return createValidationResult({
@@ -1,6 +1,12 @@
import { type FrontComponentManifest } from 'twenty-shared/application';
export type FrontComponentType = React.ComponentType<any>;
export type FrontComponentRenderFunction = (
container: HTMLElement,
) => void | (() => void);
export type FrontComponentType =
| React.ComponentType<any>
| FrontComponentRenderFunction;
export type FrontComponentConfig = Omit<
FrontComponentManifest,
+1
View File
@@ -25,6 +25,7 @@ export { RelationType } from './fields/relation-type';
export { validateFields } from './fields/validate-fields';
export type {
FrontComponentConfig,
FrontComponentRenderFunction,
FrontComponentType,
} from './front-component-config';
export { defineLogicFunction } from './logic-functions/define-logic-function';
@@ -1,7 +1,10 @@
export type FrontComponentFramework = 'react' | 'vue' | 'svelte' | 'angular';
export type FrontComponentManifest = {
universalIdentifier: string;
name?: string;
description?: string;
framework?: FrontComponentFramework;
sourceComponentPath: string;
builtComponentPath: string;
builtComponentChecksum: string;
@@ -25,7 +25,10 @@ export type {
RelationFieldManifest,
FieldManifest,
} from './fieldManifestType';
export type { FrontComponentManifest } from './frontComponentManifestType';
export type {
FrontComponentFramework,
FrontComponentManifest,
} from './frontComponentManifestType';
export type {
LogicFunctionManifest,
CronTriggerSettings,
+74 -9
View File
@@ -380,7 +380,7 @@ __metadata:
languageName: node
linkType: hard
"@ampproject/remapping@npm:^2.2.0":
"@ampproject/remapping@npm:^2.2.0, @ampproject/remapping@npm:^2.2.1":
version: 2.3.0
resolution: "@ampproject/remapping@npm:2.3.0"
dependencies:
@@ -9686,14 +9686,14 @@ __metadata:
languageName: node
linkType: hard
"@jridgewell/sourcemap-codec@npm:^1.5.5":
"@jridgewell/sourcemap-codec@npm:^1.4.15, @jridgewell/sourcemap-codec@npm:^1.5.5":
version: 1.5.5
resolution: "@jridgewell/sourcemap-codec@npm:1.5.5"
checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0
languageName: node
linkType: hard
"@jridgewell/trace-mapping@npm:0.3.31, @jridgewell/trace-mapping@npm:^0.3.31":
"@jridgewell/trace-mapping@npm:0.3.31, @jridgewell/trace-mapping@npm:^0.3.19, @jridgewell/trace-mapping@npm:^0.3.31":
version: 0.3.31
resolution: "@jridgewell/trace-mapping@npm:0.3.31"
dependencies:
@@ -23470,7 +23470,7 @@ __metadata:
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8":
"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.1, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8":
version: 1.0.8
resolution: "@types/estree@npm:1.0.8"
checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5
@@ -27850,7 +27850,7 @@ __metadata:
languageName: node
linkType: hard
"acorn@npm:^8.0.0, acorn@npm:^8.11.0, acorn@npm:^8.11.2, acorn@npm:^8.11.3, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0":
"acorn@npm:^8.0.0, acorn@npm:^8.10.0, acorn@npm:^8.11.0, acorn@npm:^8.11.2, acorn@npm:^8.11.3, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.9.0":
version: 8.15.0
resolution: "acorn@npm:8.15.0"
bin:
@@ -28623,7 +28623,7 @@ __metadata:
languageName: node
linkType: hard
"aria-query@npm:^5.0.0, aria-query@npm:^5.3.2":
"aria-query@npm:^5.0.0, aria-query@npm:^5.3.0, aria-query@npm:^5.3.2":
version: 5.3.2
resolution: "aria-query@npm:5.3.2"
checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e
@@ -29100,7 +29100,7 @@ __metadata:
languageName: node
linkType: hard
"axobject-query@npm:^4.1.0":
"axobject-query@npm:^4.0.0, axobject-query@npm:^4.1.0":
version: 4.1.0
resolution: "axobject-query@npm:4.1.0"
checksum: 10c0/c470e4f95008f232eadd755b018cb55f16c03ccf39c027b941cd8820ac6b68707ce5d7368a46756db4256fbc91bb4ead368f84f7fb034b2b7932f082f6dc0775
@@ -31508,6 +31508,19 @@ __metadata:
languageName: node
linkType: hard
"code-red@npm:^1.0.3":
version: 1.0.4
resolution: "code-red@npm:1.0.4"
dependencies:
"@jridgewell/sourcemap-codec": "npm:^1.4.15"
"@types/estree": "npm:^1.0.1"
acorn: "npm:^8.10.0"
estree-walker: "npm:^3.0.3"
periscopic: "npm:^3.1.0"
checksum: 10c0/1309f062369ae520c422d7f45b93190faea2cbc7e3fe3375918f36bb394030d0936d940601426564c30abc71b8aa8e6d1505cccd67a8620183fb01c84bcb7304
languageName: node
linkType: hard
"codemirror-graphql@npm:^2.0.13":
version: 2.0.13
resolution: "codemirror-graphql@npm:2.0.13"
@@ -34938,6 +34951,18 @@ __metadata:
languageName: node
linkType: hard
"esbuild-svelte@npm:^0.9.4":
version: 0.9.4
resolution: "esbuild-svelte@npm:0.9.4"
dependencies:
"@jridgewell/trace-mapping": "npm:^0.3.19"
peerDependencies:
esbuild: ">=0.17.0"
svelte: ">=4.2.1 <6"
checksum: 10c0/ccb3547edc5354dac23cfc49d44c992aa0a1c0b8f3c9da0c5a23fee7eaa70e18a75b1ca7f080d601dc4f5702409308aa919293423d900720914f948c7c23553e
languageName: node
linkType: hard
"esbuild@npm:0.25.5":
version: 0.25.5
resolution: "esbuild@npm:0.25.5"
@@ -41250,6 +41275,15 @@ __metadata:
languageName: node
linkType: hard
"is-reference@npm:^3.0.1":
version: 3.0.3
resolution: "is-reference@npm:3.0.3"
dependencies:
"@types/estree": "npm:^1.0.6"
checksum: 10c0/35edd284cfb4cd9e9f08973f20e276ec517eaca31f5f049598e97dbb2d05544973dde212dac30fddee5b420930bff365e2e67dcd1293d0866c6720377382e3e5
languageName: node
linkType: hard
"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1":
version: 1.2.1
resolution: "is-regex@npm:1.2.1"
@@ -44288,6 +44322,13 @@ __metadata:
languageName: node
linkType: hard
"locate-character@npm:^3.0.0":
version: 3.0.0
resolution: "locate-character@npm:3.0.0"
checksum: 10c0/9da917622395002eb1336fca8cbef1c19904e3dc0b3b8078abe8ff390106d947a86feccecd0346f0e0e19fa017623fb4ccb65263d72a76dfa36e20cc18766b6c
languageName: node
linkType: hard
"locate-path@npm:^3.0.0":
version: 3.0.0
resolution: "locate-path@npm:3.0.0"
@@ -44927,7 +44968,7 @@ __metadata:
languageName: node
linkType: hard
"magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:^0.30.3":
"magic-string@npm:^0.30.17, magic-string@npm:^0.30.21, magic-string@npm:^0.30.3, magic-string@npm:^0.30.4":
version: 0.30.21
resolution: "magic-string@npm:0.30.21"
dependencies:
@@ -50105,7 +50146,7 @@ __metadata:
languageName: node
linkType: hard
"periscopic@npm:^3.0.0":
"periscopic@npm:^3.0.0, periscopic@npm:^3.1.0":
version: 3.1.0
resolution: "periscopic@npm:3.1.0"
dependencies:
@@ -57020,6 +57061,28 @@ __metadata:
languageName: node
linkType: hard
"svelte@npm:^4.2.19":
version: 4.2.20
resolution: "svelte@npm:4.2.20"
dependencies:
"@ampproject/remapping": "npm:^2.2.1"
"@jridgewell/sourcemap-codec": "npm:^1.4.15"
"@jridgewell/trace-mapping": "npm:^0.3.18"
"@types/estree": "npm:^1.0.1"
acorn: "npm:^8.9.0"
aria-query: "npm:^5.3.0"
axobject-query: "npm:^4.0.0"
code-red: "npm:^1.0.3"
css-tree: "npm:^2.3.1"
estree-walker: "npm:^3.0.3"
is-reference: "npm:^3.0.1"
locate-character: "npm:^3.0.0"
magic-string: "npm:^0.30.4"
periscopic: "npm:^3.1.0"
checksum: 10c0/b51d5d01aa363f47c1631b6f4160754221bc8d234146d3ddeea6172928ad6554acc925c2fb39c31e5cab77ba6711f1c49f4c79e2e592eb4df4eb418b8793a0f9
languageName: node
linkType: hard
"svg-arc-to-cubic-bezier@npm:^3.0.0, svg-arc-to-cubic-bezier@npm:^3.2.0":
version: 3.2.0
resolution: "svg-arc-to-cubic-bezier@npm:3.2.0"
@@ -58430,6 +58493,7 @@ __metadata:
commander: "npm:^12.0.0"
dotenv: "npm:^16.4.0"
esbuild: "npm:^0.25.0"
esbuild-svelte: "npm:^0.9.4"
fast-glob: "npm:^3.3.0"
fs-extra: "npm:^11.2.0"
graphql: "npm:^16.8.1"
@@ -58443,6 +58507,7 @@ __metadata:
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
storybook: "npm:^10.1.11"
svelte: "npm:^4.2.19"
ts-morph: "npm:^25.0.0"
tsx: "npm:^4.7.0"
twenty-shared: "workspace:*"