diff --git a/nx.json b/nx.json index a1c3bd7046d..552198c0aec 100644 --- a/nx.json +++ b/nx.json @@ -126,7 +126,7 @@ "configurations": { "ci": { "ci": true, - "maxWorkers": 3 + "maxWorkers": 1 }, "coverage": { "coverageReporters": ["lcov", "text"] diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/tests/entities.tests.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/tests/entities.tests.ts index 5801109f944..38d213daf65 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/tests/entities.tests.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/tests/entities.tests.ts @@ -10,6 +10,7 @@ export const defineEntitiesTests = (appPath: string): void => { const sortedFiles = files.map((f) => f.toString()).sort(); expect(sortedFiles).toEqual([ + 'api-client', 'manifest.json', 'package.json', 'public', diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts b/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts index 457ab9e627c..1ccaa96eb49 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts @@ -210,8 +210,12 @@ const createSdkGeneratedResolverPlugin = (appPath: string): esbuild.Plugin => ({ }, }); +export type EsbuildWatcherFactoryOptions = RestartableWatcherOptions & { + shouldSkipTypecheck: () => boolean; +}; + export const createLogicFunctionsWatcher = ( - options: RestartableWatcherOptions, + options: EsbuildWatcherFactoryOptions, ): EsbuildWatcher => new EsbuildWatcher({ ...options, @@ -220,7 +224,7 @@ export const createLogicFunctionsWatcher = ( fileFolder: FileFolder.BuiltLogicFunction, platform: 'node', extraPlugins: [ - createTypecheckPlugin(options.appPath), + createTypecheckPlugin(options.appPath, options.shouldSkipTypecheck), createSdkGeneratedResolverPlugin(options.appPath), ], banner: NODE_ESM_CJS_BANNER, @@ -228,7 +232,7 @@ export const createLogicFunctionsWatcher = ( }); export const createFrontComponentsWatcher = ( - options: RestartableWatcherOptions, + options: EsbuildWatcherFactoryOptions, ): EsbuildWatcher => new EsbuildWatcher({ ...options, @@ -237,7 +241,7 @@ export const createFrontComponentsWatcher = ( fileFolder: FileFolder.BuiltFrontComponent, jsx: 'automatic', extraPlugins: [ - createTypecheckPlugin(options.appPath), + createTypecheckPlugin(options.appPath, options.shouldSkipTypecheck), createSdkGeneratedResolverPlugin(options.appPath), ...getFrontComponentBuildPlugins(), ], diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/typecheck-plugin.ts b/packages/twenty-sdk/src/cli/utilities/build/common/typecheck-plugin.ts index c0913e213b7..bd7f8b1ab84 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/typecheck-plugin.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/typecheck-plugin.ts @@ -72,10 +72,17 @@ const toEsbuildErrors = (errors: TypecheckError[]): esbuild.PartialMessage[] => }, })); -export const createTypecheckPlugin = (appPath: string): esbuild.Plugin => ({ +export const createTypecheckPlugin = ( + appPath: string, + shouldSkipTypecheck: () => boolean, +): esbuild.Plugin => ({ name: 'typecheck', setup: (build) => { build.onStart(async () => { + if (shouldSkipTypecheck()) { + return; + } + const errors = await runTypecheck(appPath); return { errors: toEsbuildErrors(errors) }; diff --git a/packages/twenty-sdk/src/cli/utilities/client/client-service.ts b/packages/twenty-sdk/src/cli/utilities/client/client-service.ts index 4f22cc9fbe4..201bc390912 100644 --- a/packages/twenty-sdk/src/cli/utilities/client/client-service.ts +++ b/packages/twenty-sdk/src/cli/utilities/client/client-service.ts @@ -92,6 +92,31 @@ export class ClientService { await fs.move(tempPath, outputPath); } + async ensureGeneratedClientStub({ + appPath, + }: { + appPath: string; + }): Promise { + const outputPath = this.resolveGeneratedPath(appPath); + + if (await fs.pathExists(join(outputPath, 'index.ts'))) { + return; + } + + await fs.ensureDir(join(outputPath, 'core')); + await fs.ensureDir(join(outputPath, 'metadata')); + + await fs.writeFile( + join(outputPath, 'core', 'index.ts'), + 'export class CoreApiClient {}\n', + ); + await fs.writeFile( + join(outputPath, 'metadata', 'index.ts'), + 'export class MetadataApiClient {}\n', + ); + await this.writeBarrelIndex(outputPath); + } + private resolveGeneratedPath(appPath: string): string { return join(appPath, 'node_modules', 'twenty-sdk', GENERATED_DIR); } diff --git a/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/dev-mode-orchestrator.ts b/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/dev-mode-orchestrator.ts index 5bef891ac49..f5ec1349e7a 100644 --- a/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/dev-mode-orchestrator.ts +++ b/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/dev-mode-orchestrator.ts @@ -29,6 +29,8 @@ export class DevModeOrchestrator { private syncTimer: NodeJS.Timeout | null = null; private serverCheckInterval: NodeJS.Timeout | null = null; + private clientService: ClientService; + private skipTypecheck = true; private checkServerStep: CheckServerOrchestratorStep; private ensureValidTokensStep: EnsureValidTokensOrchestratorStep; private buildManifestStep: BuildManifestOrchestratorStep; @@ -44,7 +46,7 @@ export class DevModeOrchestrator { const apiService = new ApiService({ disableInterceptors: true }); const configService = new ConfigService(); - const clientService = new ClientService(); + this.clientService = new ClientService(); const stepDeps = { state: this.state, notify: () => this.state.notify() }; this.checkServerStep = new CheckServerOrchestratorStep({ @@ -64,7 +66,7 @@ export class DevModeOrchestrator { this.uploadFilesStep = new UploadFilesOrchestratorStep(stepDeps); this.generateApiClientStep = new GenerateApiClientOrchestratorStep({ ...stepDeps, - clientService, + clientService: this.clientService, configService, }); this.syncApplicationStep = new SyncApplicationOrchestratorStep({ @@ -75,6 +77,7 @@ export class DevModeOrchestrator { ...stepDeps, scheduleSync: this.scheduleSync.bind(this), onFileBuilt: this.handleFileBuilt.bind(this), + shouldSkipTypecheck: () => this.skipTypecheck, }); } @@ -84,6 +87,10 @@ export class DevModeOrchestrator { await fs.ensureDir(outputDir); await fs.emptyDir(outputDir); + await this.clientService.ensureGeneratedClientStub({ + appPath: this.state.appPath, + }); + await this.startWatchersStep.start(); this.serverCheckInterval = setInterval(() => { @@ -200,6 +207,8 @@ export class DevModeOrchestrator { appPath: this.state.appPath, }); + this.skipTypecheck = false; + await this.uploadFilesStep.copyAndUploadApiClientFiles( this.state.appPath, ); diff --git a/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/start-watchers-orchestrator-step.ts b/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/start-watchers-orchestrator-step.ts index 71b2f3739ac..38f17424209 100644 --- a/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/start-watchers-orchestrator-step.ts +++ b/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/start-watchers-orchestrator-step.ts @@ -30,6 +30,7 @@ export class StartWatchersOrchestratorStep { private scheduleSync: () => void; private notify: () => void; private onFileBuilt: (event: FileBuiltEvent) => void; + private shouldSkipTypecheck: () => boolean; private manifestWatcher: ManifestWatcher | null = null; private logicFunctionsWatcher: EsbuildWatcher | null = null; @@ -43,11 +44,13 @@ export class StartWatchersOrchestratorStep { scheduleSync: () => void; notify: () => void; onFileBuilt: (event: FileBuiltEvent) => void; + shouldSkipTypecheck: () => boolean; }) { this.state = options.state; this.scheduleSync = options.scheduleSync; this.notify = options.notify; this.onFileBuilt = options.onFileBuilt; + this.shouldSkipTypecheck = options.shouldSkipTypecheck; } async start(): Promise { @@ -166,6 +169,7 @@ export class StartWatchersOrchestratorStep { this.logicFunctionsWatcher = createLogicFunctionsWatcher({ appPath: this.state.appPath, sourcePaths, + shouldSkipTypecheck: this.shouldSkipTypecheck, handleBuildError: this.handleFileBuildError.bind(this), handleFileBuilt: this.handleFileBuilt.bind(this), }); @@ -179,6 +183,7 @@ export class StartWatchersOrchestratorStep { this.frontComponentsWatcher = createFrontComponentsWatcher({ appPath: this.state.appPath, sourcePaths, + shouldSkipTypecheck: this.shouldSkipTypecheck, handleBuildError: this.handleFileBuildError.bind(this), handleFileBuilt: this.handleFileBuilt.bind(this), });