diff --git a/.github/workflows/ci-create-app-e2e-minimal.yaml b/.github/workflows/ci-create-app-e2e-minimal.yaml index a78b0ecc6c..4c7a343877 100644 --- a/.github/workflows/ci-create-app-e2e-minimal.yaml +++ b/.github/workflows/ci-create-app-e2e-minimal.yaml @@ -120,7 +120,7 @@ jobs: echo "--- Checking package.json references correct SDK version ---" node -e " const pkg = require('./package.json'); - const sdkVersion = pkg.dependencies['twenty-sdk']; + const sdkVersion = pkg.devDependencies['twenty-sdk']; if (!sdkVersion.startsWith('0.0.0-ci.')) { console.error('Expected twenty-sdk version to start with 0.0.0-ci., got:', sdkVersion); process.exit(1); diff --git a/packages/create-twenty-app/src/constants/template/package.json b/packages/create-twenty-app/src/constants/template/package.json index 7b4261980d..dbcdf5482e 100644 --- a/packages/create-twenty-app/src/constants/template/package.json +++ b/packages/create-twenty-app/src/constants/template/package.json @@ -17,8 +17,7 @@ "test:watch": "vitest" }, "dependencies": { - "twenty-client-sdk": "TO-BE-GENERATED", - "twenty-sdk": "TO-BE-GENERATED" + "twenty-client-sdk": "TO-BE-GENERATED" }, "devDependencies": { "@types/node": "^24.7.2", @@ -26,6 +25,7 @@ "oxlint": "^0.16.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "twenty-sdk": "TO-BE-GENERATED", "typescript": "^5.9.3", "vite-tsconfig-paths": "^4.2.1", "vitest": "^3.1.1" diff --git a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts index 066cb2ce0c..3882ec76df 100644 --- a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts +++ b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts @@ -33,9 +33,11 @@ const TEMPLATE_PACKAGE_JSON = { license: 'MIT', scripts: { twenty: 'twenty' }, dependencies: { - 'twenty-sdk': '0.0.0', 'twenty-client-sdk': '0.0.0', }, + devDependencies: { + 'twenty-sdk': '0.0.0', + }, }; describe('copyBaseApplicationProject', () => { @@ -147,7 +149,7 @@ describe('copyBaseApplicationProject', () => { join(testAppDirectory, 'package.json'), ); expect(packageJson.name).toBe('my-test-app'); - expect(packageJson.dependencies['twenty-sdk']).toBe( + expect(packageJson.devDependencies['twenty-sdk']).toBe( createTwentyAppPackageJson.version, ); expect(packageJson.dependencies['twenty-client-sdk']).toBe( diff --git a/packages/create-twenty-app/src/utils/app-template.ts b/packages/create-twenty-app/src/utils/app-template.ts index 06884d8825..caccd952cf 100644 --- a/packages/create-twenty-app/src/utils/app-template.ts +++ b/packages/create-twenty-app/src/utils/app-template.ts @@ -120,7 +120,8 @@ const updatePackageJson = async ({ const packageJson = await fs.readJson(join(appDirectory, 'package.json')); packageJson.name = appName; - packageJson.dependencies['twenty-sdk'] = createTwentyAppPackageJson.version; + packageJson.devDependencies['twenty-sdk'] = + createTwentyAppPackageJson.version; packageJson.dependencies['twenty-client-sdk'] = createTwentyAppPackageJson.version; diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx index 85b991cccd..88f244ab4e 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx @@ -8,5 +8,6 @@ icon: "wrench" - **Wrong Node version** — Need 24+. Check with `node -v`. - **Yarn 4 missing** — Run `corepack enable`. - **Dependencies broken** — `rm -rf node_modules && yarn install`. +- **`twenty-sdk` errors after upgrading to v2.8.0** — It moved from `dependencies` to `devDependencies` in v2.8.0. See [Project Structure → Dependencies](/developers/extend/apps/getting-started/project-structure#dependencies). Stuck? Ask on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322). diff --git a/packages/twenty-sdk/CHANGELOG.md b/packages/twenty-sdk/CHANGELOG.md new file mode 100644 index 0000000000..4fff109564 --- /dev/null +++ b/packages/twenty-sdk/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to the [Twenty SDK](https://www.npmjs.com/package/twenty-sdk) are documented in this file. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.8.0] + +### Breaking Changes + +- **`twenty-sdk` must now be a dev dependency.** It ships the `twenty` CLI and the build/scaffolding tooling, which only run at development and build time — it is never imported by a published app's runtime. Newly scaffolded apps already place it under `devDependencies`. Apps created before `2.8.0` must move it when upgrading: + + ```diff + "dependencies": { + "twenty-client-sdk": "^2.8.0" + - "twenty-sdk": "^2.8.0" + }, + "devDependencies": { + + "twenty-sdk": "^2.8.0" + } + ``` + + Then reinstall with `rm -rf node_modules && yarn install`. `twenty-client-sdk` stays under `dependencies` because app code imports it at runtime. + + `twenty build` now emits a warning when `twenty-sdk` is still listed under `dependencies`, so existing apps are flagged automatically. diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index 51abf19ce8..879407ae5f 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -8,6 +8,7 @@ "files": [ "dist", "README.md", + "CHANGELOG.md", "package.json" ], "scripts": { diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/build-and-validate-manifest.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/build-and-validate-manifest.ts index 4b5965b658..eb7dfca3ed 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/build-and-validate-manifest.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/build-and-validate-manifest.ts @@ -1,6 +1,7 @@ import { type EntityFilePaths } from '@/cli/utilities/build/manifest/manifest-extract-config'; import { buildManifest } from '@/cli/utilities/build/manifest/manifest-build'; import { manifestValidate } from '@/cli/utilities/build/manifest/manifest-validate'; +import { validatePackageJsonDependencies } from '@/cli/utilities/build/manifest/utils/validate-package-json-dependencies'; import { type Manifest } from 'twenty-shared/application'; export type BuildAndValidateManifestSuccess = { @@ -43,10 +44,16 @@ export const buildAndValidateManifest = async ( }; } + const packageJsonWarnings = await validatePackageJsonDependencies(appPath); + return { success: true, manifest: result.manifest, filePaths: result.filePaths, - warnings: [...result.warnings, ...validation.warnings], + warnings: [ + ...result.warnings, + ...validation.warnings, + ...packageJsonWarnings, + ], }; }; diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/__tests__/validate-package-json-dependencies.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/__tests__/validate-package-json-dependencies.spec.ts new file mode 100644 index 0000000000..666db22dd4 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/__tests__/validate-package-json-dependencies.spec.ts @@ -0,0 +1,70 @@ +import { validatePackageJsonDependencies } from '@/cli/utilities/build/manifest/utils/validate-package-json-dependencies'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +const writeTempPackageJson = async ( + packageJson: Record | null, +): Promise => { + const appPath = await mkdtemp(join(tmpdir(), 'twenty-sdk-deps-')); + + if (packageJson !== null) { + await writeFile( + join(appPath, 'package.json'), + JSON.stringify(packageJson, null, 2), + ); + } + + return appPath; +}; + +describe('validatePackageJsonDependencies', () => { + let appPath: string; + + afterEach(async () => { + if (appPath) { + await rm(appPath, { recursive: true, force: true }); + } + }); + + 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' }, + }); + + const warnings = await validatePackageJsonDependencies(appPath); + + expect(warnings).toHaveLength(1); + expect(warnings[0]).toContain('twenty-sdk'); + expect(warnings[0]).toContain('devDependencies'); + }); + + it('should not warn when twenty-sdk is listed under devDependencies', async () => { + appPath = await writeTempPackageJson({ + dependencies: { 'twenty-client-sdk': '^2.8.0' }, + devDependencies: { 'twenty-sdk': '^2.8.0' }, + }); + + const warnings = await validatePackageJsonDependencies(appPath); + + expect(warnings).toEqual([]); + }); + + it('should not warn when twenty-sdk is absent from dependencies', async () => { + appPath = await writeTempPackageJson({ + dependencies: { 'twenty-client-sdk': '^2.8.0' }, + }); + + const warnings = await validatePackageJsonDependencies(appPath); + + expect(warnings).toEqual([]); + }); + + it('should not warn when package.json is missing', async () => { + appPath = await writeTempPackageJson(null); + + const warnings = await validatePackageJsonDependencies(appPath); + + expect(warnings).toEqual([]); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/validate-package-json-dependencies.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/validate-package-json-dependencies.ts new file mode 100644 index 0000000000..f819d1ecf4 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/validate-package-json-dependencies.ts @@ -0,0 +1,30 @@ +import { pathExists, readJson } from '@/cli/utilities/file/fs-utils'; +import path from 'path'; +import { isDefined } from 'twenty-shared/utils'; + +type PackageJsonDependencies = { + dependencies?: Record; +}; + +// 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". +export const validatePackageJsonDependencies = async ( + appPath: string, +): Promise => { + const packageJsonPath = path.join(appPath, 'package.json'); + + if (!(await pathExists(packageJsonPath))) { + return []; + } + + const packageJson = await readJson(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 []; +};