Twenty sdk cli oauth (#18638)

<img width="1418" height="804" alt="image"
src="https://github.com/user-attachments/assets/de6c8222-6496-4a71-bc21-7e5e1269d5cb"
/>

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
martmull
2026-03-17 11:43:17 +01:00
committed by GitHub
parent 111debc1ce
commit 731e297147
96 changed files with 3191 additions and 2453 deletions
@@ -1,8 +1,8 @@
import { vi } from 'vitest';
import { appBuild } from '@/cli/public-operations/app-build';
import { appUninstall } from '@/cli/public-operations/app-uninstall';
import { functionExecute } from '@/cli/public-operations/function-execute';
import { appBuild } from '@/cli/operations/build';
import { appUninstall } from '@/cli/operations/uninstall';
import { functionExecute } from '@/cli/operations/execute';
import { FUNCTION_EXECUTE_APP_PATH } from '@/cli/__tests__/apps/fixture-paths';
const ADD_NUMBERS_UNIVERSAL_IDENTIFIER = 'f9e5589c-e951-4d99-85db-0a305ab53502';
@@ -5,7 +5,7 @@ import { defineFrontComponentsTests } from './tests/front-components.tests';
import { defineLogicFunctionsTests } from './tests/logic-functions.tests';
import { defineManifestTests } from './tests/manifest.tests';
describe('minimal-app app:dev', () => {
describe('minimal-app dev', () => {
beforeAll(async () => {
const result = await runAppDevInProcess({ appPath: MINIMAL_APP_PATH });
@@ -17,7 +17,7 @@ describe('minimal-app app:dev', () => {
);
throw new Error(
`app:dev did not produce manifest.json within timeout.\n${diagnostics}`,
`dev did not produce manifest.json within timeout.\n${diagnostics}`,
);
}
}, 60000);
@@ -16,11 +16,13 @@ export const defineManifestTests = (appPath: string): void => {
it('should have correct manifest content', async () => {
const manifestPath = join(appPath, '.twenty/output/manifest.json');
const manifest: Manifest = normalizeManifestForComparison(
await readJson(manifestPath),
const manifest = normalizeManifestForComparison(
await readJson<Manifest>(manifestPath),
);
expect(manifest).toEqual(EXPECTED_MANIFEST);
expect(manifest).toEqual(
normalizeManifestForComparison(EXPECTED_MANIFEST),
);
});
});
};
@@ -15,10 +15,10 @@ describe('Application: install delete and reinstall postcard-app', () => {
expect(existsSync(appPath)).toBe(true);
const result = await runCliCommand({
command: 'auth:status',
args: [appPath],
command: 'remote',
args: ['status'],
timeout: 5_000,
waitForOutput: '✓ Valid',
waitForOutput: '(valid)',
});
expect(result.success).toBe(true);
@@ -26,7 +26,7 @@ describe('Application: install delete and reinstall postcard-app', () => {
it(`should successfully install ${applicationName} application`, async () => {
await runCliCommand({
command: 'app:dev',
command: 'dev',
args: [appPath],
waitForOutput: '✓ Synced',
});
@@ -36,7 +36,7 @@ describe('Application: install delete and reinstall postcard-app', () => {
it(`should successfully delete ${applicationName} application`, async () => {
await runCliCommand({
command: 'app:uninstall',
command: 'uninstall',
args: [appPath, '-y'],
waitForOutput: 'Application uninstalled successfully',
});
@@ -44,7 +44,7 @@ describe('Application: install delete and reinstall postcard-app', () => {
it(`should successfully re-install ${applicationName} application`, async () => {
await runCliCommand({
command: 'app:dev',
command: 'dev',
args: [appPath],
waitForOutput: '✓ Synced',
});
@@ -4,7 +4,7 @@ import { POSTCARD_APP_PATH } from '@/cli/__tests__/apps/fixture-paths';
import { defineEntitiesTests } from './tests/entities.tests';
import { defineManifestTests } from './tests/manifest.tests';
describe('postcard-app app:dev', () => {
describe('postcard-app dev', () => {
beforeAll(async () => {
const result = await runAppDevInProcess({ appPath: POSTCARD_APP_PATH });
@@ -16,7 +16,7 @@ describe('postcard-app app:dev', () => {
);
throw new Error(
`app:dev did not produce manifest.json within timeout.\n${diagnostics}`,
`dev did not produce manifest.json within timeout.\n${diagnostics}`,
);
}
}, 60000);
@@ -1 +1 @@
export const SERVER_URL = 'http://localhost:3000';
export const SERVER_URL = process.env.TWENTY_API_URL ?? 'http://localhost:3000';
@@ -11,12 +11,13 @@ beforeAll(async () => {
await ensureDir(path.dirname(testConfigPath));
const configFile = {
profiles: {
default: {
remotes: {
local: {
apiUrl: process.env.TWENTY_API_URL,
apiKey: process.env.TWENTY_API_KEY,
},
},
defaultRemote: 'local',
};
await writeFile(testConfigPath, JSON.stringify(configFile, null, 2));
@@ -1,5 +1,10 @@
import { type Manifest } from 'twenty-shared/application';
const sortById = <T extends { universalIdentifier: string }>(items: T[]): T[] =>
[...items].sort((a, b) =>
a.universalIdentifier.localeCompare(b.universalIdentifier),
);
export const normalizeManifestForComparison = <T extends Manifest>(
manifest: T,
): T => ({
@@ -16,14 +21,31 @@ export const normalizeManifestForComparison = <T extends Manifest>(
? '[checksum]'
: null,
},
logicFunctions: manifest.logicFunctions?.map((fn) => ({
...fn,
builtHandlerChecksum: fn.builtHandlerChecksum ? '[checksum]' : null,
})),
frontComponents: manifest.frontComponents?.map((component) => ({
...component,
builtComponentChecksum: component.builtComponentChecksum
? '[checksum]'
: '',
})),
objects: sortById(
manifest.objects.map((object) => ({
...object,
fields: sortById(object.fields),
})),
),
fields: sortById(manifest.fields),
roles: sortById(manifest.roles),
skills: sortById(manifest.skills),
agents: sortById(manifest.agents),
views: sortById(manifest.views),
navigationMenuItems: sortById(manifest.navigationMenuItems),
pageLayouts: sortById(manifest.pageLayouts),
logicFunctions: sortById(
manifest.logicFunctions?.map((fn) => ({
...fn,
builtHandlerChecksum: fn.builtHandlerChecksum ? '[checksum]' : null,
})),
),
frontComponents: sortById(
manifest.frontComponents?.map((component) => ({
...component,
builtComponentChecksum: component.builtComponentChecksum
? '[checksum]'
: '',
})),
),
});
@@ -1,7 +1,7 @@
import { join } from 'path';
import { OUTPUT_DIR } from 'twenty-shared/application';
import { AppDevCommand } from '@/cli/commands/app/app-dev';
import { AppDevCommand } from '@/cli/commands/dev';
import { pathExists } from '@/cli/utilities/file/fs-utils';
export type RunAppDevResult = {
@@ -5,23 +5,11 @@ const mockApiService = {
generateApplicationToken: vi.fn().mockResolvedValue({
success: true,
data: {
applicationAccessToken: { token: 'mock-access-token', expiresAt: '' },
applicationRefreshToken: { token: 'mock-refresh-token', expiresAt: '' },
},
}),
renewApplicationToken: vi.fn().mockResolvedValue({
success: true,
data: {
applicationAccessToken: {
token: 'mock-renewed-access-token',
expiresAt: '',
},
applicationRefreshToken: {
token: 'mock-renewed-refresh-token',
expiresAt: '',
},
accessToken: { token: 'mock-access-token', expiresAt: '' },
refreshToken: { token: 'mock-refresh-token', expiresAt: '' },
},
}),
refreshToken: vi.fn().mockResolvedValue('mock-renewed-access-token'),
findApplicationRegistrationByUniversalIdentifier: vi
.fn()
.mockResolvedValue({ success: true, data: null }),
@@ -47,7 +35,7 @@ vi.mock('@/cli/utilities/api/api-service', () => ({
ApiService: class {
validateAuth = mockApiService.validateAuth;
generateApplicationToken = mockApiService.generateApplicationToken;
renewApplicationToken = mockApiService.renewApplicationToken;
refreshToken = mockApiService.refreshToken;
findApplicationRegistrationByUniversalIdentifier =
mockApiService.findApplicationRegistrationByUniversalIdentifier;
createApplicationRegistration =