First Application POC (#14382)

Quick proof of concept for twenty-apps + twenty-cli, with local
development / hot reload

Let's discuss it!



https://github.com/user-attachments/assets/c6789936-cd5f-4110-a265-863a6ac1af2d
This commit is contained in:
Félix Malfait
2025-09-10 15:12:38 +02:00
committed by GitHub
parent 9a05daa624
commit 30a2164980
79 changed files with 5515 additions and 365 deletions
@@ -0,0 +1,98 @@
import chalk from 'chalk';
import * as chokidar from 'chokidar';
import { ApiService } from '../services/api.service';
import { resolveAppPath } from '../utils/app-path-resolver';
import { syncApp } from '../utils/app-sync';
export class AppDevCommand {
private apiService = new ApiService();
async execute(options: {
path?: string;
debounce: string;
verbose?: boolean;
}): Promise<void> {
try {
const appPath = await resolveAppPath(options.path, options.verbose);
const debounceMs = parseInt(options.debounce, 10);
this.logStartupInfo(appPath, debounceMs, options.verbose);
await syncApp(appPath, this.apiService);
const watcher = this.setupFileWatcher(
appPath,
debounceMs,
options.verbose,
);
this.setupGracefulShutdown(watcher);
} catch (error) {
console.error(
chalk.red('Development mode failed:'),
error instanceof Error ? error.message : error,
);
process.exit(1);
}
}
private logStartupInfo(
appPath: string,
debounceMs: number,
verbose?: boolean,
): void {
console.log(chalk.blue('🚀 Starting Twenty Application Development Mode'));
console.log(chalk.gray(`📁 App Path: ${appPath}`));
console.log(chalk.gray(`⏱️ Debounce: ${debounceMs}ms`));
console.log(chalk.gray(`🔧 Verbose: ${verbose ? 'On' : 'Off'}`));
console.log('');
}
private setupFileWatcher(
appPath: string,
debounceMs: number,
verbose?: boolean,
): chokidar.FSWatcher {
const watcher = chokidar.watch(appPath, {
ignored: /node_modules|\.git/,
persistent: true,
});
let timeout: NodeJS.Timeout | null = null;
const debouncedSync = () => {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(async () => {
console.log(chalk.blue('🔄 Changes detected, syncing...'));
await syncApp(appPath, this.apiService);
console.log(
chalk.gray('👀 Watching for changes... (Press Ctrl+C to stop)'),
);
}, debounceMs);
};
watcher.on('change', (filePath) => {
if (verbose) {
console.log(chalk.gray(`📝 ${filePath} changed`));
}
debouncedSync();
});
console.log(
chalk.gray('👀 Watching for changes... (Press Ctrl+C to stop)'),
);
return watcher;
}
private setupGracefulShutdown(watcher: chokidar.FSWatcher): void {
process.on('SIGINT', () => {
console.log(chalk.yellow('\n🛑 Stopping development mode...'));
watcher.close();
process.exit(0);
});
}
}