Move twenty-client-sdk to dev dep (#21611)

# Introduction
The `twenty-client-sdk` is always provided and injected at runtime by
the twenty-server instance
Which mean that even if in your app locally you're using
twenty-client-sdk `1.0` installing this app on twenty instance `2.0`
will result in injecting another `twenty-client-sdk`

That's the expected behavior and tradeof

The twenty-app devdep should only be used to guide local devxp following
typesafety and so on

A user can still locally generated its own twenty-client-sdk and publish
it if necessary

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21611?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Paul Rastoin
2026-06-15 16:46:22 +02:00
committed by GitHub
parent 09694b2f3b
commit fdab89ae02
11 changed files with 125 additions and 34 deletions
@@ -29,7 +29,7 @@ describe('validatePackageJsonDependencies', () => {
it('should warn when twenty-sdk is listed under dependencies', async () => {
appPath = await writeTempPackageJson({
dependencies: { 'twenty-sdk': '^2.8.0', 'twenty-client-sdk': '^2.8.0' },
dependencies: { 'twenty-sdk': '^2.8.0' },
});
const warnings = await validatePackageJsonDependencies(appPath);
@@ -39,10 +39,40 @@ describe('validatePackageJsonDependencies', () => {
expect(warnings[0]).toContain('devDependencies');
});
it('should not warn when twenty-sdk is listed under devDependencies', async () => {
it('should warn when twenty-client-sdk is listed under dependencies', async () => {
appPath = await writeTempPackageJson({
dependencies: { 'twenty-client-sdk': '^2.8.0' },
devDependencies: { 'twenty-sdk': '^2.8.0' },
});
const warnings = await validatePackageJsonDependencies(appPath);
expect(warnings).toHaveLength(1);
expect(warnings[0]).toContain('twenty-client-sdk');
expect(warnings[0]).toContain('devDependencies');
});
it('should warn for both SDK packages when both are listed under dependencies', async () => {
appPath = await writeTempPackageJson({
dependencies: { 'twenty-sdk': '^2.8.0', 'twenty-client-sdk': '^2.8.0' },
});
const warnings = await validatePackageJsonDependencies(appPath);
expect(warnings).toHaveLength(2);
expect(warnings.some((warning) => warning.includes('twenty-sdk'))).toBe(
true,
);
expect(
warnings.some((warning) => warning.includes('twenty-client-sdk')),
).toBe(true);
});
it('should not warn when both SDK packages are listed under devDependencies', async () => {
appPath = await writeTempPackageJson({
devDependencies: {
'twenty-sdk': '^2.8.0',
'twenty-client-sdk': '^2.8.0',
},
});
const warnings = await validatePackageJsonDependencies(appPath);
@@ -50,9 +80,9 @@ describe('validatePackageJsonDependencies', () => {
expect(warnings).toEqual([]);
});
it('should not warn when twenty-sdk is absent from dependencies', async () => {
it('should not warn when the SDK packages are absent from dependencies', async () => {
appPath = await writeTempPackageJson({
dependencies: { 'twenty-client-sdk': '^2.8.0' },
dependencies: { 'some-other-package': '^1.0.0' },
});
const warnings = await validatePackageJsonDependencies(appPath);
@@ -6,9 +6,21 @@ type PackageJsonDependencies = {
dependencies?: Record<string, string>;
};
// twenty-sdk ships the CLI and build/scaffolding tooling used only at dev and
// build time — it is never imported by the published app's runtime. Keeping it
// in "dependencies" ships dead weight, so it must live in "devDependencies".
// Neither Twenty SDK package needs to be resolved by the published app at
// runtime, so both must live in "devDependencies":
// - twenty-sdk ships the CLI and build/scaffolding tooling used only at dev and
// build time, and is never imported by the published app's runtime.
// - twenty-client-sdk is imported by app code but is provided at runtime by
// Twenty's injected SDK (Lambda SDK layer / server-served modules), so the
// app's installed copy is only needed for typecheck/build.
// Keeping either under "dependencies" pulls it into the Lambda deps layer.
const BUILD_TIME_DEPENDENCY_WARNINGS: Record<string, string> = {
'twenty-sdk':
'"twenty-sdk" is listed under "dependencies" in package.json. It is a build-time only tool and should be moved to "devDependencies".',
'twenty-client-sdk':
'"twenty-client-sdk" is listed under "dependencies" in package.json. It is provided at runtime by Twenty\'s injected SDK and should be moved to "devDependencies".',
};
export const validatePackageJsonDependencies = async (
appPath: string,
): Promise<string[]> => {
@@ -20,11 +32,9 @@ export const validatePackageJsonDependencies = async (
const packageJson = await readJson<PackageJsonDependencies>(packageJsonPath);
if (isDefined(packageJson.dependencies?.['twenty-sdk'])) {
return [
'"twenty-sdk" is listed under "dependencies" in package.json. It is a build-time only tool and should be moved to "devDependencies".',
];
}
return [];
return Object.entries(BUILD_TIME_DEPENDENCY_WARNINGS)
.filter(([packageName]) =>
isDefined(packageJson.dependencies?.[packageName]),
)
.map(([, warning]) => warning);
};