Add sdk build (#17335)

## Summary

Adds `app:build` command as a one-shot version of `app:dev` - builds
manifest, functions, and front components once then exits (no watching).

**Changes:**
- Added `watch` option to `FunctionsWatcher` and
`FrontComponentsWatcher` to support both watch and one-shot modes
- Created `AppBuildCommand` reusing the same build logic as `app:dev`
with `watch: false`
- Added integration tests for `app:build` on both `rich-app` and
`root-app`
- Updated manifest types: `handlerPath` → `sourceHandlerPath` +
`builtHandlerPath`, `componentPath` → `sourceComponentPath` +
`builtComponentPath`
- Updated test app `package.json` scripts to match `create-twenty-app`
template
This commit is contained in:
Charles Bochet
2026-01-22 14:36:40 +01:00
committed by GitHub
parent d6f088f720
commit d537d941a7
25 changed files with 1050 additions and 86 deletions
@@ -94,9 +94,7 @@ export const registerCommands = (program: Command): void => {
...options,
appPath: formatPath(appPath),
});
if (!result.success) {
process.exit(1);
}
process.exit(result.success ? 0 : 1);
} catch {
process.exit(1);
}
@@ -1,28 +1,79 @@
import { type ApiResponse } from '@/cli/utilities/api/types/api-response.types';
import { runManifestBuild } from '@/cli/utilities/build/manifest/manifest-build';
import { createLogger } from '@/cli/utilities/build/common/logger';
import { FrontComponentsWatcher } from '@/cli/utilities/build/front-components/front-component-watcher';
import { FunctionsWatcher } from '@/cli/utilities/build/functions/function-watcher';
import { runManifestBuild, type ManifestBuildResult } from '@/cli/utilities/build/manifest/manifest-build';
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
import chalk from 'chalk';
export type BuildCommandOptions = {
const initLogger = createLogger('init');
export type AppBuildOptions = {
appPath?: string;
};
export class AppBuildCommand {
async execute(options: BuildCommandOptions): Promise<ApiResponse<null>> {
const appPath = options.appPath ?? CURRENT_EXECUTION_DIRECTORY;
private functionsBuilder: FunctionsWatcher | null = null;
private frontComponentsBuilder: FrontComponentsWatcher | null = null;
console.log(chalk.blue('🚀 Building Twenty Application'));
console.log(chalk.gray(`📁 App Path: ${appPath}`));
private appPath: string = '';
async execute(options: AppBuildOptions): Promise<ApiResponse<null>> {
this.appPath = options.appPath ?? CURRENT_EXECUTION_DIRECTORY;
initLogger.log('🚀 Building Twenty Application');
initLogger.log(`📁 App Path: ${this.appPath}`);
console.log('');
const { manifest } = await runManifestBuild(appPath);
const buildResult = await this.runBuild();
if (!manifest) {
if (!buildResult) {
return { success: false, error: 'Build failed' };
}
console.log(chalk.green('✅ Build completed successfully'));
initLogger.success('✅ Build completed successfully');
return { success: true, data: null };
}
private async runBuild(): Promise<ManifestBuildResult | null> {
const buildResult = await runManifestBuild(this.appPath);
if (!buildResult.manifest) {
return null;
}
await this.buildFunctions(buildResult);
await this.buildFrontComponents(buildResult);
await this.cleanup();
return buildResult;
}
private async buildFunctions(buildResult: ManifestBuildResult): Promise<void> {
this.functionsBuilder = new FunctionsWatcher({
appPath: this.appPath,
buildResult,
watch: false,
});
await this.functionsBuilder.start();
}
private async buildFrontComponents(buildResult: ManifestBuildResult): Promise<void> {
this.frontComponentsBuilder = new FrontComponentsWatcher({
appPath: this.appPath,
buildResult,
watch: false,
});
await this.frontComponentsBuilder.start();
}
private async cleanup(): Promise<void> {
await this.functionsBuilder?.close();
await this.frontComponentsBuilder?.close();
await manifestExtractFromFileServer.closeViteServer();
}
}