From 9b8eacb7f76535b8a224dd6fe138c0a842a0749e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:43:08 +0100 Subject: [PATCH] [FRONT COMPONENTS] Expose twenty UI in twenty sdk (#17645) - An app will declare its `twenty-sdk` version in the manifest. - `twenty-sdk` will be served by a CDN to be imported in front component host. - When we render the host we will load twenty-ui through the correct version of the sdk We will proceed this way because twenty-ui is not mature enough yet to be deployed as its own package. So instead, since the sdk is already deployed as its own package, we will serve the ui with it. It allows us to have versioning to handle breaking changes in twenty-ui. --- packages/twenty-sdk/package.json | 24 ++- packages/twenty-sdk/project.json | 40 ++-- .../twenty-sdk/scripts/generateBarrels.ts | 42 ++++- packages/twenty-sdk/src/ui/index.ts | 20 ++ packages/twenty-sdk/vite.config.ts | 16 +- yarn.lock | 172 +++++++++--------- 6 files changed, 189 insertions(+), 125 deletions(-) create mode 100644 packages/twenty-sdk/src/ui/index.ts diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index 46cd25b29d..5e76a71378 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -5,12 +5,13 @@ "module": "dist/index.mjs", "types": "dist/index.d.ts", "bin": { - "twenty": "dist/cli.cjs" + "twenty": "dist/cli/index.cjs" }, "files": [ "dist", "front-component", - "front-component-constants" + "front-component-constants", + "ui" ], "scripts": { "build": "npx rimraf dist && npx vite build" @@ -31,13 +32,18 @@ }, "./front-component": { "types": "./dist/front-component/index.d.ts", - "import": "./dist/front-component.mjs", - "require": "./dist/front-component.cjs" + "import": "./dist/front-component/index.mjs", + "require": "./dist/front-component/index.cjs" }, "./front-component-constants": { "types": "./dist/front-component-constants/index.d.ts", - "import": "./dist/front-component-constants.mjs", - "require": "./dist/front-component-constants.cjs" + "import": "./dist/front-component-constants/index.mjs", + "require": "./dist/front-component-constants/index.cjs" + }, + "./ui": { + "types": "./dist/ui/index.d.ts", + "import": "./dist/ui/index.mjs", + "require": "./dist/ui/index.cjs" } }, "license": "AGPL-3.0", @@ -58,7 +64,7 @@ "fs-extra": "^11.2.0", "graphql": "^16.8.1", "graphql-sse": "^2.5.4", - "ink": "^6.6.0", + "ink": "^5.1.1", "inquirer": "^10.0.0", "jsonc-parser": "^3.2.0", "lodash.camelcase": "^4.3.0", @@ -83,6 +89,7 @@ "@types/react-dom": "18.2.22", "ts-morph": "^25.0.0", "tsx": "^4.7.0", + "twenty-ui": "workspace:*", "vite-plugin-dts": "^4.5.4", "wait-on": "^7.2.0" }, @@ -97,6 +104,9 @@ ], "front-component-constants": [ "dist/front-component-constants/index.d.ts" + ], + "ui": [ + "dist/ui/index.d.ts" ] } } diff --git a/packages/twenty-sdk/project.json b/packages/twenty-sdk/project.json index db21b14de0..ebf092150d 100644 --- a/packages/twenty-sdk/project.json +++ b/packages/twenty-sdk/project.json @@ -3,30 +3,23 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/twenty-sdk/src", "projectType": "library", - "tags": [ - "scope:sdk", - "scope:shared" - ], + "tags": ["scope:sdk", "scope:shared"], "targets": { "build": { - "dependsOn": [ - "generateBarrels", - "^build" - ], + "dependsOn": ["generateBarrels", "^build"], "outputs": [ "{projectRoot}/dist", "{projectRoot}/front-component/package.json", "{projectRoot}/front-component/dist", "{projectRoot}/front-component-constants/package.json", - "{projectRoot}/front-component-constants/dist" + "{projectRoot}/front-component-constants/dist", + "{projectRoot}/ui/package.json", + "{projectRoot}/ui/dist" ] }, "dev": { "executor": "nx:run-commands", - "dependsOn": [ - "generateBarrels", - "^build" - ], + "dependsOn": ["generateBarrels", "^build"], "options": { "cwd": "packages/twenty-sdk", "command": "npx rimraf dist && npx vite build --watch" @@ -34,9 +27,7 @@ }, "start": { "executor": "nx:run-commands", - "dependsOn": [ - "build" - ], + "dependsOn": ["build"], "options": { "cwd": "packages/twenty-sdk", "command": "node dist/cli.cjs" @@ -45,10 +36,7 @@ "generateBarrels": { "executor": "nx:run-commands", "cache": true, - "inputs": [ - "production", - "{projectRoot}/scripts/generateBarrels.ts" - ], + "inputs": ["production", "{projectRoot}/scripts/generateBarrels.ts"], "outputs": [ "{projectRoot}/src/index.ts", "{projectRoot}/src/*/index.ts", @@ -61,16 +49,12 @@ "typecheck": {}, "lint": { "options": { - "lintFilePatterns": [ - "{projectRoot}/src/**/*.{ts,json}" - ], + "lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"], "maxWarnings": 0 }, "configurations": { "ci": { - "lintFilePatterns": [ - "{projectRoot}/src/**/*.{ts,json}" - ], + "lintFilePatterns": ["{projectRoot}/src/**/*.{ts,json}"], "maxWarnings": 0 }, "fix": {} @@ -78,9 +62,7 @@ }, "test": { "executor": "@nx/vitest:test", - "outputs": [ - "{workspaceRoot}/coverage/{projectRoot}" - ], + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], "options": { "config": "{projectRoot}/vitest.config.ts" }, diff --git a/packages/twenty-sdk/scripts/generateBarrels.ts b/packages/twenty-sdk/scripts/generateBarrels.ts index 7b27f61e00..5c86bcf4a7 100644 --- a/packages/twenty-sdk/scripts/generateBarrels.ts +++ b/packages/twenty-sdk/scripts/generateBarrels.ts @@ -35,6 +35,21 @@ const EXCLUDED_DIRECTORIES = [ ] as const; const ROOT_DIRECTORIES = ['sdk']; +// Modules that re-export from external packages instead of local files +const EXTERNAL_REEXPORT_MODULES: Record = { + ui: `export * from 'twenty-ui'; +export * from 'twenty-ui/accessibility'; +export * from 'twenty-ui/components'; +export * from 'twenty-ui/display'; +export * from 'twenty-ui/feedback'; +export * from 'twenty-ui/input'; +export * from 'twenty-ui/json-visualizer'; +export * from 'twenty-ui/layout'; +export * from 'twenty-ui/navigation'; +export * from 'twenty-ui/theme'; +export * from 'twenty-ui/utilities';`, +}; + const prettierConfigFile = prettier.resolveConfigFile(); if (prettierConfigFile == null) { throw new Error('Prettier config file not found'); @@ -223,8 +238,8 @@ const generateModulePackageExports = (moduleDirectories: string[]) => { ...acc, [`./${moduleName}`]: { types: `./dist/${moduleName}/index.d.ts`, - import: `./dist/${moduleName}.mjs`, - require: `./dist/${moduleName}.cjs`, + import: `./dist/${moduleName}/index.mjs`, + require: `./dist/${moduleName}/index.cjs`, }, }; }, {}); @@ -490,11 +505,17 @@ const main = () => { ROOT_DIRECTORIES.includes(getLastPathFolder(dir)), ); + // Separate modules that re-export from external packages + const externalReexportModuleNames = Object.keys(EXTERNAL_REEXPORT_MODULES); + const localModuleDirectories = moduleDirectories.filter( + (dir) => !externalReexportModuleNames.includes(getLastPathFolder(dir)), + ); + const otherBarrelDirectories = moduleDirectories.filter( (dir) => !ROOT_DIRECTORIES.includes(getLastPathFolder(dir)), ); - const exportsByBarrel = retrieveExportsByBarrel(moduleDirectories); + const exportsByBarrel = retrieveExportsByBarrel(localModuleDirectories); const moduleIndexFiles = generateModuleIndexFiles(exportsByBarrel); const packageJsonConfig = computePackageJsonFilesAndExportsConfig( @@ -508,6 +529,21 @@ const main = () => { writeInPackageJson(packageJsonConfig); moduleIndexFiles.forEach(createTypeScriptFile); + // Generate index files for modules that re-export from external packages + for (const [moduleName, content] of Object.entries( + EXTERNAL_REEXPORT_MODULES, + )) { + const moduleDirectory = path.join(SRC_PATH, moduleName); + if (!fs.existsSync(moduleDirectory)) { + fs.mkdirSync(moduleDirectory, { recursive: true }); + } + createTypeScriptFile({ + path: moduleDirectory, + filename: INDEX_FILENAME, + content, + }); + } + // Ensure top-level src/index.ts re-exports the root directories barrel so consumers can `import * from "twenty-sdk"` // We intentionally keep this file minimal: it delegates to the generated src//index.ts if (rootDirectory) { diff --git a/packages/twenty-sdk/src/ui/index.ts b/packages/twenty-sdk/src/ui/index.ts new file mode 100644 index 0000000000..2c682cedef --- /dev/null +++ b/packages/twenty-sdk/src/ui/index.ts @@ -0,0 +1,20 @@ +/* + * _____ _ + *|_ _|_ _____ _ __ | |_ _ _ + * | | \ \ /\ / / _ \ '_ \| __| | | | Auto-generated file + * | | \ V V / __/ | | | |_| |_| | Any edits to this will be overridden + * |_| \_/\_/ \___|_| |_|\__|\__, | + * |___/ + */ + +export * from 'twenty-ui'; +export * from 'twenty-ui/accessibility'; +export * from 'twenty-ui/components'; +export * from 'twenty-ui/display'; +export * from 'twenty-ui/feedback'; +export * from 'twenty-ui/input'; +export * from 'twenty-ui/json-visualizer'; +export * from 'twenty-ui/layout'; +export * from 'twenty-ui/navigation'; +export * from 'twenty-ui/theme'; +export * from 'twenty-ui/utilities'; diff --git a/packages/twenty-sdk/vite.config.ts b/packages/twenty-sdk/vite.config.ts index 6edb73536d..dc6b6356d7 100644 --- a/packages/twenty-sdk/vite.config.ts +++ b/packages/twenty-sdk/vite.config.ts @@ -31,7 +31,7 @@ const entryFileNames = (chunk: any, extension: 'cjs' | 'mjs') => { if (moduleDirectory === 'src') { return `${chunk.name}.${extension}`; } - return `${moduleDirectory}.${extension}`; + return `${moduleDirectory}/index.${extension}`; }; const copyTwentyPackagesInVendor = (packages: string[]) => { @@ -115,8 +115,20 @@ export default defineConfig(() => { outDir: 'dist', lib: { entry: entries, name: 'twenty-sdk' }, rollupOptions: { + onwarn: (warning, warn) => { + // Suppress "use client" directive warnings from framer-motion + if ( + warning.code === 'MODULE_LEVEL_DIRECTIVE' && + warning.message.includes('"use client"') + ) { + return; + } + warn(warning); + }, external: [ - ...Object.keys((packageJson as any).dependencies || {}), + ...Object.keys((packageJson as any).dependencies || {}).filter( + (dep) => dep !== 'twenty-ui', + ), 'path', 'fs', 'fs/promises', diff --git a/yarn.lock b/yarn.lock index ffd2393532..76ce587d62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -147,6 +147,16 @@ __metadata: languageName: node linkType: hard +"@alcalzone/ansi-tokenize@npm:^0.1.3": + version: 0.1.3 + resolution: "@alcalzone/ansi-tokenize@npm:0.1.3" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^4.0.0" + checksum: 10c0/b88c5708271bb64ce132fc80dac8d5b87fc1699bf3abfdf10ecae40dbb56ab82460818f5746ecdd9870a00be9854e039d5cb3a121b808d0ff6f5bc7d0146cb38 + languageName: node + linkType: hard + "@alcalzone/ansi-tokenize@npm:^0.2.1": version: 0.2.2 resolution: "@alcalzone/ansi-tokenize@npm:0.2.2" @@ -27580,15 +27590,6 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^7.2.0": - version: 7.2.0 - resolution: "ansi-escapes@npm:7.2.0" - dependencies: - environment: "npm:^1.0.0" - checksum: 10c0/b562fd995761fa12f33be316950ee58fda489e125d331bcd9131434969a2eb55dc14e9405f214dcf4697c9d67c576ba0baf6e8f3d52058bf9222c97560b220cb - languageName: node - linkType: hard - "ansi-regex@npm:^2.0.0": version: 2.1.1 resolution: "ansi-regex@npm:2.1.1" @@ -30661,16 +30662,6 @@ __metadata: languageName: node linkType: hard -"cli-truncate@npm:^5.1.1": - version: 5.1.1 - resolution: "cli-truncate@npm:5.1.1" - dependencies: - slice-ansi: "npm:^7.1.0" - string-width: "npm:^8.0.0" - checksum: 10c0/3842920829a62f3e041ce39199050c42706c3c9c756a4efc8b86d464e102d1fa031d8b1b9b2e3bb36e1017c763558275472d031bdc884c1eff22a2f20e4f6b0a - languageName: node - linkType: hard - "cli-ux@npm:^4.9.0": version: 4.9.3 resolution: "cli-ux@npm:4.9.3" @@ -34223,6 +34214,18 @@ __metadata: languageName: node linkType: hard +"es-toolkit@npm:^1.22.0": + version: 1.44.0 + resolution: "es-toolkit@npm:1.44.0" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + checksum: 10c0/b80ff52ddc85ba26914cda57c9d4e46379ccc38c60dc097ef0d065cc0b20f95a16cf8d537969eea600b51c6687b5900a6cce67489db16d5ccc14d47597a29c34 + languageName: node + linkType: hard + "es-toolkit@npm:^1.39.10": version: 1.41.0 resolution: "es-toolkit@npm:1.41.0" @@ -37335,7 +37338,7 @@ __metadata: languageName: node linkType: hard -"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.0, get-east-asian-width@npm:^1.3.1": +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1": version: 1.4.0 resolution: "get-east-asian-width@npm:1.4.0" checksum: 10c0/4e481d418e5a32061c36fbb90d1b225a254cc5b2df5f0b25da215dcd335a3c111f0c2023ffda43140727a9cafb62dac41d022da82c08f31083ee89f714ee3b83 @@ -39666,6 +39669,47 @@ __metadata: languageName: node linkType: hard +"ink@npm:^5.1.1": + version: 5.2.1 + resolution: "ink@npm:5.2.1" + dependencies: + "@alcalzone/ansi-tokenize": "npm:^0.1.3" + ansi-escapes: "npm:^7.0.0" + ansi-styles: "npm:^6.2.1" + auto-bind: "npm:^5.0.1" + chalk: "npm:^5.3.0" + cli-boxes: "npm:^3.0.0" + cli-cursor: "npm:^4.0.0" + cli-truncate: "npm:^4.0.0" + code-excerpt: "npm:^4.0.0" + es-toolkit: "npm:^1.22.0" + indent-string: "npm:^5.0.0" + is-in-ci: "npm:^1.0.0" + patch-console: "npm:^2.0.0" + react-reconciler: "npm:^0.29.0" + scheduler: "npm:^0.23.0" + signal-exit: "npm:^3.0.7" + slice-ansi: "npm:^7.1.0" + stack-utils: "npm:^2.0.6" + string-width: "npm:^7.2.0" + type-fest: "npm:^4.27.0" + widest-line: "npm:^5.0.0" + wrap-ansi: "npm:^9.0.0" + ws: "npm:^8.18.0" + yoga-layout: "npm:~3.2.1" + peerDependencies: + "@types/react": ">=18.0.0" + react: ">=18.0.0" + react-devtools-core: ^4.19.1 + peerDependenciesMeta: + "@types/react": + optional: true + react-devtools-core: + optional: true + checksum: 10c0/0e2607712783353fbe99438ca4220db3d5874c7ae1a07e2b232f7539489b13a9db929b3046eedeccf318a3ffa222a572287dbe2307c848b8e7f6ec4bba39e550 + languageName: node + linkType: hard + "ink@npm:^6.0.1": version: 6.4.0 resolution: "ink@npm:6.4.0" @@ -39706,46 +39750,6 @@ __metadata: languageName: node linkType: hard -"ink@npm:^6.6.0": - version: 6.6.0 - resolution: "ink@npm:6.6.0" - dependencies: - "@alcalzone/ansi-tokenize": "npm:^0.2.1" - ansi-escapes: "npm:^7.2.0" - ansi-styles: "npm:^6.2.1" - auto-bind: "npm:^5.0.1" - chalk: "npm:^5.6.0" - cli-boxes: "npm:^3.0.0" - cli-cursor: "npm:^4.0.0" - cli-truncate: "npm:^5.1.1" - code-excerpt: "npm:^4.0.0" - es-toolkit: "npm:^1.39.10" - indent-string: "npm:^5.0.0" - is-in-ci: "npm:^2.0.0" - patch-console: "npm:^2.0.0" - react-reconciler: "npm:^0.33.0" - signal-exit: "npm:^3.0.7" - slice-ansi: "npm:^7.1.0" - stack-utils: "npm:^2.0.6" - string-width: "npm:^8.1.0" - type-fest: "npm:^4.27.0" - widest-line: "npm:^5.0.0" - wrap-ansi: "npm:^9.0.0" - ws: "npm:^8.18.0" - yoga-layout: "npm:~3.2.1" - peerDependencies: - "@types/react": ">=19.0.0" - react: ">=19.0.0" - react-devtools-core: ^6.1.2 - peerDependenciesMeta: - "@types/react": - optional: true - react-devtools-core: - optional: true - checksum: 10c0/60fe53f122f025c2ee849fb79aba13923fc2ca0ff9ff9af05725742ba613ccad5f1b7e7aba36f6b4a64a9e1c2eb8caa1c678d47993f1281d71408df12b4b82b6 - languageName: node - linkType: hard - "inline-source-map@npm:~0.6.0": version: 0.6.3 resolution: "inline-source-map@npm:0.6.3" @@ -40354,6 +40358,15 @@ __metadata: languageName: node linkType: hard +"is-in-ci@npm:^1.0.0": + version: 1.0.0 + resolution: "is-in-ci@npm:1.0.0" + bin: + is-in-ci: cli.js + checksum: 10c0/98f9cec4c35aece4cf731abf35ebf28359a9b0324fac810da05b842515d9ccb33b8999c1d9a679f0362e1a4df3292065c38d7f86327b1387fa667bc0150f4898 + languageName: node + linkType: hard + "is-in-ci@npm:^2.0.0": version: 2.0.0 resolution: "is-in-ci@npm:2.0.0" @@ -51804,6 +51817,18 @@ __metadata: languageName: node linkType: hard +"react-reconciler@npm:^0.29.0": + version: 0.29.2 + resolution: "react-reconciler@npm:0.29.2" + dependencies: + loose-envify: "npm:^1.1.0" + scheduler: "npm:^0.23.2" + peerDependencies: + react: ^18.3.1 + checksum: 10c0/94f48ddc348a974256cf13c859f5a94efdb0cd72e04c51b1a4d5c72a8b960ccd35df2196057ee6a4cbcb26145e12b01e3f9ba3b183fddb901414db36a07cbf43 + languageName: node + linkType: hard + "react-reconciler@npm:^0.32.0": version: 0.32.0 resolution: "react-reconciler@npm:0.32.0" @@ -51815,17 +51840,6 @@ __metadata: languageName: node linkType: hard -"react-reconciler@npm:^0.33.0": - version: 0.33.0 - resolution: "react-reconciler@npm:0.33.0" - dependencies: - scheduler: "npm:^0.27.0" - peerDependencies: - react: ^19.2.0 - checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a - languageName: node - linkType: hard - "react-redux@npm:^8.1.3": version: 8.1.3 resolution: "react-redux@npm:8.1.3" @@ -53922,7 +53936,7 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.2": +"scheduler@npm:^0.23.0, scheduler@npm:^0.23.2": version: 0.23.2 resolution: "scheduler@npm:0.23.2" dependencies: @@ -55670,16 +55684,6 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^8.0.0, string-width@npm:^8.1.0": - version: 8.1.0 - resolution: "string-width@npm:8.1.0" - dependencies: - get-east-asian-width: "npm:^1.3.0" - strip-ansi: "npm:^7.1.0" - checksum: 10c0/749b5d0dab2532b4b6b801064230f4da850f57b3891287023117ab63a464ad79dd208f42f793458f48f3ad121fe2e1f01dd525ff27ead957ed9f205e27406593 - languageName: node - linkType: hard - "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -57696,7 +57700,7 @@ __metadata: fs-extra: "npm:^11.2.0" graphql: "npm:^16.8.1" graphql-sse: "npm:^2.5.4" - ink: "npm:^6.6.0" + ink: "npm:^5.1.1" inquirer: "npm:^10.0.0" jsonc-parser: "npm:^3.2.0" lodash.camelcase: "npm:^4.3.0" @@ -57714,7 +57718,7 @@ __metadata: wait-on: "npm:^7.2.0" zod: "npm:^4.1.11" bin: - twenty: dist/cli.cjs + twenty: dist/cli/index.cjs languageName: unknown linkType: soft