diff --git a/packages/twenty-sdk/src/app-seeds/rich-app/__integration__/app-build/app-build.integration.spec.ts b/packages/twenty-sdk/src/app-seeds/rich-app/__integration__/app-build/app-build.integration.spec.ts new file mode 100644 index 00000000000..820dd4d3502 --- /dev/null +++ b/packages/twenty-sdk/src/app-seeds/rich-app/__integration__/app-build/app-build.integration.spec.ts @@ -0,0 +1,67 @@ +import { readdir, rm } from 'node:fs/promises'; +import { join } from 'path'; + +import { appBuild } from '@/cli/public-operations/app-build'; +import { pathExists } from '@/cli/utilities/file/fs-utils'; +import { GENERATED_DIR, OUTPUT_DIR } from 'twenty-shared/application'; + +const APP_PATH = join(__dirname, '../..'); + +describe('rich-app app:build', () => { + let buildResult: Awaited>; + + beforeAll(async () => { + const generatedPath = join( + APP_PATH, + 'node_modules', + 'twenty-sdk', + GENERATED_DIR, + ); + + await rm(generatedPath, { recursive: true, force: true }); + + buildResult = await appBuild({ appPath: APP_PATH }); + }, 60_000); + + it('should complete build successfully', () => { + expect(buildResult.success).toBe(true); + }); + + it('should produce manifest in output directory', async () => { + const manifestPath = join(APP_PATH, OUTPUT_DIR, 'manifest.json'); + + expect(await pathExists(manifestPath)).toBe(true); + }); + + it('should produce built logic function files', async () => { + const outputDir = join(APP_PATH, OUTPUT_DIR); + const files = await readdir(outputDir, { recursive: true }); + const functionFiles = files + .map((file) => file.toString()) + .filter((file) => file.endsWith('.function.mjs')) + .sort(); + + expect(functionFiles.length).toBeGreaterThan(0); + }); + + // app:build only creates client stubs (empty CoreApiClient / MetadataApiClient) + // because it doesn't sync with the server or generate the real API client. + // Logic functions that use CoreApiClient will get an empty class at runtime. + // When this is fixed, update this test to assert the client IS fully generated. + // See: https://github.com/twentyhq/twenty/pull/18460 + it('should only produce client stubs (not a fully generated CoreApiClient)', async () => { + const clientIndexPath = join( + APP_PATH, + 'node_modules', + 'twenty-sdk', + GENERATED_DIR, + 'core', + 'index.ts', + ); + + const { readFile } = await import('node:fs/promises'); + const content = await readFile(clientIndexPath, 'utf-8'); + + expect(content).toBe('export class CoreApiClient {}\n'); + }); +}); diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/__e2e__/function-execute.e2e-spec.ts b/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/__e2e__/function-execute.e2e-spec.ts index 5b5165229aa..e6618e256b1 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/__e2e__/function-execute.e2e-spec.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/__e2e__/function-execute.e2e-spec.ts @@ -76,4 +76,24 @@ describe('functionExecute E2E', () => { error: { code: 'FUNCTION_NOT_FOUND' }, }); }); + + // Verifies that CoreApiClient is fully generated (not just stubs) after + // appGenerateClient. If app:build is used without client generation, the + // bundled CoreApiClient would be an empty stub and this test would fail. + // See: https://github.com/twentyhq/twenty/pull/18460 + it('should execute a function that uses CoreApiClient with real query/mutation methods', async () => { + const result = await functionExecute({ + appPath: APP_PATH, + functionName: 'check-core-client', + }); + + expect(result).toMatchObject({ + success: true, + data: { + functionName: 'check-core-client', + status: 'SUCCESS', + data: { hasQuery: true, hasMutation: true }, + }, + }); + }); }); diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/src/logic-functions/check-core-client.function.ts b/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/src/logic-functions/check-core-client.function.ts new file mode 100644 index 00000000000..6645dd04650 --- /dev/null +++ b/packages/twenty-sdk/src/cli/__tests__/apps/function-execute-app/src/logic-functions/check-core-client.function.ts @@ -0,0 +1,26 @@ +import { CoreApiClient } from 'twenty-sdk/generated'; +import { defineLogicFunction } from '@/sdk'; + +export const CHECK_CORE_CLIENT_UNIVERSAL_IDENTIFIER = + 'b7c8d9e0-1a2b-4c3d-8e5f-6a7b8c9d0e1f'; + +const checkCoreClientHandler = () => { + const client = new CoreApiClient(); + + return { + hasQuery: typeof client.query === 'function', + hasMutation: typeof client.mutation === 'function', + }; +}; + +export default defineLogicFunction({ + universalIdentifier: CHECK_CORE_CLIENT_UNIVERSAL_IDENTIFIER, + name: 'check-core-client', + timeoutSeconds: 5, + handler: checkCoreClientHandler, + httpRouteTriggerSettings: { + path: '/check-core-client', + httpMethod: 'GET', + isAuthRequired: false, + }, +}); diff --git a/packages/twenty-sdk/tsconfig.json b/packages/twenty-sdk/tsconfig.json index db12e9ed30a..a1005264c31 100644 --- a/packages/twenty-sdk/tsconfig.json +++ b/packages/twenty-sdk/tsconfig.json @@ -36,6 +36,8 @@ "src/front-component-renderer/host/generated/host-component-registry.ts", "src/front-component-renderer/remote/generated/remote-components.ts", "src/front-component-renderer/remote/generated/remote-elements.ts", - "src/front-component-renderer/__stories__/**/*" + "src/front-component-renderer/__stories__/**/*", + "src/app-seeds/*/src/**/*", + "src/cli/__tests__/apps/*/src/**/*" ] }