Merge twenty-cli into twenty-sdk (#16150)

- Moves twenty-cli content into twenty-sdk
- add a new twenty-sdk:0.1.0 version
- this new twenty-sdk exports a cli command called 'twenty' (like
twenty-cli before)
- deprecates twenty-cli
- simplify app init command base-project
- use `twenty-sdk:0.1.0` in base project
- move the "twenty-sdk/application" barrel to "twenty-sdk"
- add `create-twenty-app` package

<img width="1512" height="919" alt="image"
src="https://github.com/user-attachments/assets/007bef45-4e71-419a-9213-cebed376adbf"
/>

<img width="1506" height="929" alt="image"
src="https://github.com/user-attachments/assets/3de2fec6-1624-4923-ae13-f4e1cf165eb5"
/>
This commit is contained in:
martmull
2025-12-01 11:44:35 +01:00
committed by GitHub
parent 3f08a0c901
commit e498367e2f
85 changed files with 1077 additions and 1560 deletions
@@ -0,0 +1,48 @@
import { existsSync } from 'fs';
import { AppSyncCommand } from '../../commands/app-sync.command';
import { AppUninstallCommand } from '../../commands/app-uninstall.command';
import { COVERED_APPLICATION_FOLDERS } from './constants/covered-applications-folder.constant';
import { getTestedApplicationPath } from './utils/get-tested-application-path.util';
describe.each(COVERED_APPLICATION_FOLDERS)(
'Application: "%s" install delete and reinstall test suite',
(applicationName) => {
const syncCommand = new AppSyncCommand();
const deleteCommand = new AppUninstallCommand();
const appPath = getTestedApplicationPath(applicationName);
beforeAll(async () => {
expect(existsSync(appPath)).toBe(true);
});
afterAll(async () => {
const result = await deleteCommand.execute({
appPath,
askForConfirmation: false,
});
expect(result.success).toBe(true);
});
it(`should successfully install ${applicationName} application`, async () => {
const result = await syncCommand.execute(appPath);
expect(result.success).toBe(true);
});
it(`should successfully delete ${applicationName} application`, async () => {
const result = await deleteCommand.execute({
appPath,
askForConfirmation: false,
});
expect(result.success).toBe(true);
});
it(`should successfully re-install ${applicationName} application`, async () => {
const result = await syncCommand.execute(appPath);
expect(result.success).toBe(true);
});
},
);
@@ -0,0 +1 @@
export const COVERED_APPLICATION_FOLDERS = ['hello-world'] as const;
@@ -0,0 +1 @@
export const SERVER_URL = 'http://localhost:3000';
@@ -0,0 +1,7 @@
import { type TwentyConfig } from '../../../types/config.types';
export const testConfig: TwentyConfig = {
apiUrl: 'http://localhost:3000',
apiKey:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
};
@@ -0,0 +1,12 @@
import axios from 'axios';
import { SERVER_URL } from './constants/server-url.constant';
describe('Twenty Server Health Check (E2E)', () => {
const HEALTH_ENDPOINT = `${SERVER_URL}/healthz`;
it('should return 200 for health', async () => {
const response = await axios.get(HEALTH_ENDPOINT);
expect(response.status).toBe(200);
expect(response.data).toBeDefined();
});
});
@@ -0,0 +1,12 @@
import { ConfigService } from '../../services/config.service';
import { testConfig } from './constants/testConfig';
beforeAll(() => {
jest
.spyOn(ConfigService.prototype, 'getConfig')
.mockResolvedValue(testConfig);
});
afterAll(() => {
jest.restoreAllMocks();
});
@@ -0,0 +1,13 @@
import { exec } from 'child_process';
export default async () =>
new Promise<void>((resolve) => {
exec('pkill -f "nest start" || true', (error: unknown) => {
if (error) {
console.log('No server processes to kill');
} else {
console.log('✅ Server processes cleaned up');
}
resolve();
});
});
@@ -0,0 +1,10 @@
import path from 'path';
export const getTestedApplicationPath = (relativePath: string): string => {
const twentyAppsPath = path.resolve(
__dirname,
'../../../../../../twenty-apps',
);
return path.join(twentyAppsPath, relativePath);
};