From 7c713577f185960ab969c6da8dc49d3b80879917 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Mon, 19 Jan 2026 10:36:40 +0100 Subject: [PATCH] Rework folder structure (#17229) image --- ...tions-install-delete-reinstall.e2e-spec.ts | 6 +- packages/twenty-sdk/src/cli/cli.ts | 10 +- .../src/cli/commands/app.command.ts | 296 ++++++++++-------- .../{app-add.command.ts => app/app-add.ts} | 0 .../app-build.ts} | 11 - .../app-generate.ts} | 0 .../{app-logs.command.ts => app/app-logs.ts} | 0 .../src/cli/commands/app/app-publish.ts | 8 + .../{app-sync.command.ts => app/app-sync.ts} | 0 .../src/cli/commands/app/app-test.ts | 8 + .../app-uninstall.ts} | 0 .../{app-dev.command.ts => app/app-watch.ts} | 2 +- .../twenty-sdk/src/cli/commands/app/index.ts | 9 + .../src/cli/commands/auth.command.ts | 162 ---------- .../src/cli/commands/auth/auth-login.ts | 84 +++++ .../src/cli/commands/auth/auth-logout.ts | 24 ++ .../src/cli/commands/auth/auth-status.ts | 37 +++ .../src/cli/commands/auth/auth-switch.ts | 8 + .../twenty-sdk/src/cli/commands/auth/index.ts | 4 + .../src/cli/commands/entity/entity-add.ts | 8 + .../src/cli/commands/entity/index.ts | 1 + .../src/cli/commands/instance/index.ts | 3 + .../src/cli/commands/instance/instance-run.ts | 8 + .../cli/commands/instance/instance-setup.ts | 8 + .../cli/commands/instance/instance-upgrade.ts | 8 + .../twenty-sdk/src/cli/commands/sdk/index.ts | 1 + .../src/cli/commands/sdk/sdk-generate.ts | 8 + 27 files changed, 394 insertions(+), 320 deletions(-) rename packages/twenty-sdk/src/cli/commands/{app-add.command.ts => app/app-add.ts} (100%) rename packages/twenty-sdk/src/cli/commands/{app-build.command.ts => app/app-build.ts} (78%) rename packages/twenty-sdk/src/cli/commands/{app-generate.command.ts => app/app-generate.ts} (100%) rename packages/twenty-sdk/src/cli/commands/{app-logs.command.ts => app/app-logs.ts} (100%) create mode 100644 packages/twenty-sdk/src/cli/commands/app/app-publish.ts rename packages/twenty-sdk/src/cli/commands/{app-sync.command.ts => app/app-sync.ts} (100%) create mode 100644 packages/twenty-sdk/src/cli/commands/app/app-test.ts rename packages/twenty-sdk/src/cli/commands/{app-uninstall.command.ts => app/app-uninstall.ts} (100%) rename packages/twenty-sdk/src/cli/commands/{app-dev.command.ts => app/app-watch.ts} (98%) create mode 100644 packages/twenty-sdk/src/cli/commands/app/index.ts delete mode 100644 packages/twenty-sdk/src/cli/commands/auth.command.ts create mode 100644 packages/twenty-sdk/src/cli/commands/auth/auth-login.ts create mode 100644 packages/twenty-sdk/src/cli/commands/auth/auth-logout.ts create mode 100644 packages/twenty-sdk/src/cli/commands/auth/auth-status.ts create mode 100644 packages/twenty-sdk/src/cli/commands/auth/auth-switch.ts create mode 100644 packages/twenty-sdk/src/cli/commands/auth/index.ts create mode 100644 packages/twenty-sdk/src/cli/commands/entity/entity-add.ts create mode 100644 packages/twenty-sdk/src/cli/commands/entity/index.ts create mode 100644 packages/twenty-sdk/src/cli/commands/instance/index.ts create mode 100644 packages/twenty-sdk/src/cli/commands/instance/instance-run.ts create mode 100644 packages/twenty-sdk/src/cli/commands/instance/instance-setup.ts create mode 100644 packages/twenty-sdk/src/cli/commands/instance/instance-upgrade.ts create mode 100644 packages/twenty-sdk/src/cli/commands/sdk/index.ts create mode 100644 packages/twenty-sdk/src/cli/commands/sdk/sdk-generate.ts diff --git a/packages/twenty-sdk/src/cli/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts b/packages/twenty-sdk/src/cli/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts index fc81b99af0..c3779b0db4 100644 --- a/packages/twenty-sdk/src/cli/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts +++ b/packages/twenty-sdk/src/cli/__tests__/e2e/applications-install-delete-reinstall.e2e-spec.ts @@ -1,7 +1,7 @@ -import { existsSync } from 'fs'; -import { AppSyncCommand } from '@/cli/commands/app-sync.command'; -import { AppUninstallCommand } from '@/cli/commands/app-uninstall.command'; import { getTestedApplicationPath } from '@/cli/__tests__/e2e/utils/get-tested-application-path.util'; +import { AppSyncCommand } from '@/cli/commands/app/app-sync'; +import { AppUninstallCommand } from '@/cli/commands/app/app-uninstall'; +import { existsSync } from 'fs'; import { inspect } from 'util'; inspect.defaultOptions.depth = 10; diff --git a/packages/twenty-sdk/src/cli/cli.ts b/packages/twenty-sdk/src/cli/cli.ts index 7484e8dc3b..cab9dd81ff 100644 --- a/packages/twenty-sdk/src/cli/cli.ts +++ b/packages/twenty-sdk/src/cli/cli.ts @@ -1,10 +1,9 @@ #!/usr/bin/env node -import { inspect } from 'util'; +import { registerCommands } from '@/cli/commands/app.command'; +import { ConfigService } from '@/cli/services/config.service'; import chalk from 'chalk'; import { Command, CommanderError } from 'commander'; -import { AppCommand } from '@/cli/commands/app.command'; -import { AuthCommand } from '@/cli/commands/auth.command'; -import { ConfigService } from '@/cli/services/config.service'; +import { inspect } from 'util'; import packageJson from '../../package.json'; inspect.defaultOptions.depth = 10; @@ -33,8 +32,7 @@ program.hook('preAction', (thisCommand) => { ); }); -program.addCommand(new AuthCommand().getCommand()); -program.addCommand(new AppCommand().getCommand()); +registerCommands(program); program.exitOverride(); diff --git a/packages/twenty-sdk/src/cli/commands/app.command.ts b/packages/twenty-sdk/src/cli/commands/app.command.ts index 4b3181a61c..4c43b42a44 100644 --- a/packages/twenty-sdk/src/cli/commands/app.command.ts +++ b/packages/twenty-sdk/src/cli/commands/app.command.ts @@ -1,164 +1,186 @@ +import { formatPath } from '@/cli/utils/format-path'; import chalk from 'chalk'; -import { Command } from 'commander'; +import type { Command } from 'commander'; import { AppAddCommand, isSyncableEntity, SyncableEntity, -} from './app-add.command'; -import { AppUninstallCommand } from '@/cli/commands/app-uninstall.command'; -import { AppDevCommand } from '@/cli/commands/app-dev.command'; -import { AppSyncCommand } from '@/cli/commands/app-sync.command'; -import { formatPath } from '@/cli/utils/format-path'; -import { AppGenerateCommand } from '@/cli/commands/app-generate.command'; -import { AppLogsCommand } from '@/cli/commands/app-logs.command'; -import { AppBuildCommand } from '@/cli/commands/app-build.command'; +} from './app/app-add'; +import { AppBuildCommand } from './app/app-build'; +import { AppGenerateCommand } from './app/app-generate'; +import { AppLogsCommand } from './app/app-logs'; +import { AppSyncCommand } from './app/app-sync'; +import { AppUninstallCommand } from './app/app-uninstall'; +import { AppWatchCommand } from './app/app-watch'; +import { AuthLoginCommand } from './auth/auth-login'; +import { AuthLogoutCommand } from './auth/auth-logout'; +import { AuthStatusCommand } from './auth/auth-status'; -export class AppCommand { - private devCommand = new AppDevCommand(); - private syncCommand = new AppSyncCommand(); - private uninstallCommand = new AppUninstallCommand(); - private addCommand = new AppAddCommand(); - private generateCommand = new AppGenerateCommand(); - private logsCommand = new AppLogsCommand(); - private buildCommand = new AppBuildCommand(); +export const registerCommands = (program: Command): void => { + // Auth commands + const loginCommand = new AuthLoginCommand(); + const logoutCommand = new AuthLogoutCommand(); + const statusCommand = new AuthStatusCommand(); - getCommand(): Command { - const appCommand = new Command('app'); - appCommand.description('Application development commands'); + program + .command('auth:login') + .description('Authenticate with Twenty') + .option('--api-key ', 'API key for authentication') + .option('--api-url ', 'Twenty API URL') + .action(async (options) => { + await loginCommand.execute(options); + }); - appCommand - .command('dev [appPath]') - .description('Watch and sync local application changes') - .option('-d, --debounce ', 'Debounce delay in milliseconds', '1000') - .action(async (appPath, options) => { - await this.devCommand.execute({ + program + .command('auth:logout') + .description('Remove authentication credentials') + .action(async () => { + await logoutCommand.execute(); + }); + + program + .command('auth:status') + .description('Check authentication status') + .action(async () => { + await statusCommand.execute(); + }); + + // App commands + const watchCommand = new AppWatchCommand(); + const syncCommand = new AppSyncCommand(); + const uninstallCommand = new AppUninstallCommand(); + const addCommand = new AppAddCommand(); + const generateCommand = new AppGenerateCommand(); + const logsCommand = new AppLogsCommand(); + const buildCommand = new AppBuildCommand(); + + program + .command('app:dev [appPath]') + .description('Watch and sync local application changes') + .option('-d, --debounce ', 'Debounce delay in milliseconds', '1000') + .action(async (appPath, options) => { + await watchCommand.execute({ + ...options, + appPath: formatPath(appPath), + }); + }); + + program + .command('app:build [appPath]') + .description('Build application for deployment') + .option('-w, --watch', 'Watch for changes and rebuild') + .option('-t, --tarball', 'Create a tarball after build') + .action(async (appPath, options) => { + try { + const result = await buildCommand.execute({ ...options, appPath: formatPath(appPath), }); - }); - - appCommand - .command('build [appPath]') - .description('Build application for deployment') - .option('-w, --watch', 'Watch for changes and rebuild') - .option('-t, --tarball', 'Create a tarball after build') - .action(async (appPath, options) => { - try { - const result = await this.buildCommand.execute({ - ...options, - appPath: formatPath(appPath), - }); - if (!result.success) { - process.exit(1); - } - } catch { + if (!result.success) { process.exit(1); } - }); + } catch { + process.exit(1); + } + }); - appCommand - .command('sync [appPath]') - .description('Sync application to Twenty') - .action(async (appPath?: string) => { - try { - const result = await this.syncCommand.execute(formatPath(appPath)); - if (!result.success) { - process.exit(1); - } - } catch { + program + .command('app:sync [appPath]') + .description('Sync application to Twenty') + .action(async (appPath?: string) => { + try { + const result = await syncCommand.execute(formatPath(appPath)); + if (!result.success) { process.exit(1); } - }); + } catch { + process.exit(1); + } + }); - appCommand - .command('uninstall [appPath]') - .description('Uninstall application from Twenty') - .action(async (appPath?: string) => { - try { - const result = await this.uninstallCommand.execute({ - appPath: formatPath(appPath), - askForConfirmation: true, - }); - if (!result.success) { - process.exit(1); - } - } catch { + program + .command('app:uninstall [appPath]') + .description('Uninstall application from Twenty') + .action(async (appPath?: string) => { + try { + const result = await uninstallCommand.execute({ + appPath: formatPath(appPath), + askForConfirmation: true, + }); + if (!result.success) { process.exit(1); } - }); + } catch { + process.exit(1); + } + }); - // Keeping to avoid breaking changes - appCommand - .command('delete [appPath]', { hidden: true }) - .description('Delete application from Twenty') - .action(async (appPath?: string) => { - try { - const result = await this.uninstallCommand.execute({ - appPath: formatPath(appPath), - askForConfirmation: true, - }); - if (!result.success) { - process.exit(1); - } - } catch { + // Keeping to avoid breaking changes + program + .command('app:delete [appPath]', { hidden: true }) + .description('Delete application from Twenty') + .action(async (appPath?: string) => { + try { + const result = await uninstallCommand.execute({ + appPath: formatPath(appPath), + askForConfirmation: true, + }); + if (!result.success) { process.exit(1); } - }); + } catch { + process.exit(1); + } + }); - appCommand - .command('add [entityType]') - .option('--path ', 'Path in which the entity should be created.') - .description( - `Add a new entity to your application (${Object.values(SyncableEntity).join('|')})`, - ) - .action(async (entityType?: string, options?: { path?: string }) => { - if (entityType && !isSyncableEntity(entityType)) { - console.error( - chalk.red( - `Invalid entity type "${entityType}". Must be one of: ${Object.values(SyncableEntity).join('|')}`, - ), - ); - process.exit(1); - } - await this.addCommand.execute( - entityType as SyncableEntity, - options?.path, + program + .command('app:add [entityType]') + .option('--path ', 'Path in which the entity should be created.') + .description( + `Add a new entity to your application (${Object.values(SyncableEntity).join('|')})`, + ) + .action(async (entityType?: string, options?: { path?: string }) => { + if (entityType && !isSyncableEntity(entityType)) { + console.error( + chalk.red( + `Invalid entity type "${entityType}". Must be one of: ${Object.values(SyncableEntity).join('|')}`, + ), ); - }); + process.exit(1); + } + await addCommand.execute(entityType as SyncableEntity, options?.path); + }); - appCommand - .command('generate [appPath]') - .description('Generate Twenty client') - .action(async (appPath?: string) => { - await this.generateCommand.execute(formatPath(appPath)); - }); + program + .command('app:generate [appPath]') + .description('Generate Twenty client') + .action(async (appPath?: string) => { + await generateCommand.execute(formatPath(appPath)); + }); - appCommand - .command('logs [appPath]') - .option( - '-u, --functionUniversalIdentifier ', - 'Only show logs for the function with this universal ID', - ) - .option( - '-n, --functionName ', - 'Only show logs for the function with this name', - ) - .description('Watch application function logs') - .action( - async ( - appPath?: string, - options?: { - functionUniversalIdentifier?: string; - functionName?: string; - }, - ) => { - await this.logsCommand.execute({ - ...options, - appPath: formatPath(appPath), - }); + program + .command('app:logs [appPath]') + .option( + '-u, --functionUniversalIdentifier ', + 'Only show logs for the function with this universal ID', + ) + .option( + '-n, --functionName ', + 'Only show logs for the function with this name', + ) + .description('Watch application function logs') + .action( + async ( + appPath?: string, + options?: { + functionUniversalIdentifier?: string; + functionName?: string; }, - ); - - return appCommand; - } -} + ) => { + await logsCommand.execute({ + ...options, + appPath: formatPath(appPath), + }); + }, + ); +}; diff --git a/packages/twenty-sdk/src/cli/commands/app-add.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-add.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/app-add.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-add.ts diff --git a/packages/twenty-sdk/src/cli/commands/app-build.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-build.ts similarity index 78% rename from packages/twenty-sdk/src/cli/commands/app-build.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-build.ts index 1210cb7f54..945e29fc27 100644 --- a/packages/twenty-sdk/src/cli/commands/app-build.command.ts +++ b/packages/twenty-sdk/src/cli/commands/app/app-build.ts @@ -9,17 +9,6 @@ export type BuildCommandOptions = { tarball?: boolean; }; -/** - * AppBuildCommand handles the `twenty app build` CLI command. - * - * This command transpiles TypeScript applications into distributable - * JavaScript bundles using Vite. - * - * Usage: - * - `npx twenty app build [appPath]` - One-time build - * - `npx twenty app build --watch [appPath]` - Watch mode with incremental rebuilds - * - `npx twenty app build --tarball [appPath]` - Build + create .tar.gz - */ export class AppBuildCommand { private buildService = new BuildService(); diff --git a/packages/twenty-sdk/src/cli/commands/app-generate.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-generate.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/app-generate.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-generate.ts diff --git a/packages/twenty-sdk/src/cli/commands/app-logs.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-logs.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/app-logs.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-logs.ts diff --git a/packages/twenty-sdk/src/cli/commands/app/app-publish.ts b/packages/twenty-sdk/src/cli/commands/app/app-publish.ts new file mode 100644 index 0000000000..261761141e --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/app/app-publish.ts @@ -0,0 +1,8 @@ +// Placeholder for app publish command +// TODO: Implement application publishing functionality + +export class AppPublishCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/app-sync.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-sync.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/app-sync.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-sync.ts diff --git a/packages/twenty-sdk/src/cli/commands/app/app-test.ts b/packages/twenty-sdk/src/cli/commands/app/app-test.ts new file mode 100644 index 0000000000..a597ebb0ca --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/app/app-test.ts @@ -0,0 +1,8 @@ +// Placeholder for app test command +// TODO: Implement application testing functionality + +export class AppTestCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/app-uninstall.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-uninstall.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/app-uninstall.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-uninstall.ts diff --git a/packages/twenty-sdk/src/cli/commands/app-dev.command.ts b/packages/twenty-sdk/src/cli/commands/app/app-watch.ts similarity index 98% rename from packages/twenty-sdk/src/cli/commands/app-dev.command.ts rename to packages/twenty-sdk/src/cli/commands/app/app-watch.ts index a79dc4a683..d4ef942817 100644 --- a/packages/twenty-sdk/src/cli/commands/app-dev.command.ts +++ b/packages/twenty-sdk/src/cli/commands/app/app-watch.ts @@ -8,7 +8,7 @@ import { loadManifest } from '@/cli/utils/load-manifest'; import { displayWarnings } from '@/cli/utils/display-warnings'; import { displayErrors } from '@/cli/utils/display-errors'; -export class AppDevCommand { +export class AppWatchCommand { private apiService = new ApiService(); async execute(options: { diff --git a/packages/twenty-sdk/src/cli/commands/app/index.ts b/packages/twenty-sdk/src/cli/commands/app/index.ts new file mode 100644 index 0000000000..f33ba4d3a9 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/app/index.ts @@ -0,0 +1,9 @@ +export { AppWatchCommand } from './app-watch'; +export { AppBuildCommand, type BuildCommandOptions } from './app-build'; +export { AppSyncCommand } from './app-sync'; +export { AppUninstallCommand } from './app-uninstall'; +export { AppLogsCommand } from './app-logs'; +export { AppTestCommand } from './app-test'; +export { AppPublishCommand } from './app-publish'; +export { AppAddCommand, SyncableEntity, isSyncableEntity } from './app-add'; +export { AppGenerateCommand } from './app-generate'; diff --git a/packages/twenty-sdk/src/cli/commands/auth.command.ts b/packages/twenty-sdk/src/cli/commands/auth.command.ts deleted file mode 100644 index d0fc1edb22..0000000000 --- a/packages/twenty-sdk/src/cli/commands/auth.command.ts +++ /dev/null @@ -1,162 +0,0 @@ -import chalk from 'chalk'; -import { Command } from 'commander'; -import inquirer from 'inquirer'; -import { ApiService } from '@/cli/services/api.service'; -import { ConfigService } from '@/cli/services/config.service'; - -export class AuthCommand { - private configService = new ConfigService(); - private apiService = new ApiService(); - - getCommand(): Command { - const authCommand = new Command('auth'); - authCommand.description('Authentication commands'); - - authCommand - .command('login') - .description('Authenticate with Twenty') - .option('--api-key ', 'API key for authentication') - .option('--api-url ', 'Twenty API URL') - .action(async (options) => { - await this.login(options); - }); - - authCommand - .command('logout') - .description('Remove authentication credentials') - .action(async () => { - await this.logout(); - }); - - authCommand - .command('status') - .description('Check authentication status') - .action(async () => { - await this.status(); - }); - - return authCommand; - } - - private async login(options: { - apiKey?: string; - apiUrl?: string; - }): Promise { - try { - let { apiKey, apiUrl } = options; - - // Get current config - const config = await this.configService.getConfig(); - - // Prompt for missing values - if (!apiUrl) { - const urlAnswer = await inquirer.prompt([ - { - type: 'input', - name: 'apiUrl', - message: 'Twenty API URL:', - default: config.apiUrl, - validate: (input) => { - try { - new URL(input); - return true; - } catch { - return 'Please enter a valid URL'; - } - }, - }, - ]); - apiUrl = urlAnswer.apiUrl; - } - - if (!apiKey) { - const keyAnswer = await inquirer.prompt([ - { - type: 'password', - name: 'apiKey', - message: 'API Key:', - mask: '*', - validate: (input) => input.length > 0 || 'API key is required', - }, - ]); - apiKey = keyAnswer.apiKey; - } - - // Update config - await this.configService.setConfig({ - apiUrl, - apiKey, - }); - - // Validate authentication - const isValid = await this.apiService.validateAuth(); - - if (isValid) { - const activeWorkspace = ConfigService.getActiveWorkspace(); - console.log( - chalk.green( - `✓ Successfully authenticated with Twenty (workspace: ${activeWorkspace})`, - ), - ); - } else { - console.log( - chalk.red('✗ Authentication failed. Please check your credentials.'), - ); - process.exit(1); - } - } catch (error) { - console.error( - chalk.red('Login failed:'), - error instanceof Error ? error.message : error, - ); - process.exit(1); - } - } - - private async logout(): Promise { - try { - await this.configService.clearConfig(); - const activeWorkspace = ConfigService.getActiveWorkspace(); - console.log( - chalk.green( - `✓ Successfully logged out (workspace: ${activeWorkspace})`, - ), - ); - } catch (error) { - console.error( - chalk.red('Logout failed:'), - error instanceof Error ? error.message : error, - ); - process.exit(1); - } - } - - private async status(): Promise { - try { - const activeWorkspace = ConfigService.getActiveWorkspace(); - const config = await this.configService.getConfig(); - - console.log(chalk.blue('Authentication Status:')); - console.log(`Workspace: ${activeWorkspace}`); - console.log(`API URL: ${config.apiUrl}`); - console.log( - `API Key: ${config.apiKey ? '***' + config.apiKey.slice(-4) : 'Not set'}`, - ); - - if (config.apiKey) { - const isValid = await this.apiService.validateAuth(); - console.log( - `Status: ${isValid ? chalk.green('✓ Valid') : chalk.red('✗ Invalid')}`, - ); - } else { - console.log(`Status: ${chalk.yellow('⚠ Not authenticated')}`); - } - } catch (error) { - console.error( - chalk.red('Status check failed:'), - error instanceof Error ? error.message : error, - ); - process.exit(1); - } - } -} diff --git a/packages/twenty-sdk/src/cli/commands/auth/auth-login.ts b/packages/twenty-sdk/src/cli/commands/auth/auth-login.ts new file mode 100644 index 0000000000..82174669b8 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/auth/auth-login.ts @@ -0,0 +1,84 @@ +import chalk from 'chalk'; +import inquirer from 'inquirer'; +import { ApiService } from '@/cli/services/api.service'; +import { ConfigService } from '@/cli/services/config.service'; + +export class AuthLoginCommand { + private configService = new ConfigService(); + private apiService = new ApiService(); + + async execute(options: { + apiKey?: string; + apiUrl?: string; + }): Promise { + try { + let { apiKey, apiUrl } = options; + + // Get current config + const config = await this.configService.getConfig(); + + // Prompt for missing values + if (!apiUrl) { + const urlAnswer = await inquirer.prompt([ + { + type: 'input', + name: 'apiUrl', + message: 'Twenty API URL:', + default: config.apiUrl, + validate: (input) => { + try { + new URL(input); + return true; + } catch { + return 'Please enter a valid URL'; + } + }, + }, + ]); + apiUrl = urlAnswer.apiUrl; + } + + if (!apiKey) { + const keyAnswer = await inquirer.prompt([ + { + type: 'password', + name: 'apiKey', + message: 'API Key:', + mask: '*', + validate: (input) => input.length > 0 || 'API key is required', + }, + ]); + apiKey = keyAnswer.apiKey; + } + + // Update config + await this.configService.setConfig({ + apiUrl, + apiKey, + }); + + // Validate authentication + const isValid = await this.apiService.validateAuth(); + + if (isValid) { + const activeWorkspace = ConfigService.getActiveWorkspace(); + console.log( + chalk.green( + `✓ Successfully authenticated with Twenty (workspace: ${activeWorkspace})`, + ), + ); + } else { + console.log( + chalk.red('✗ Authentication failed. Please check your credentials.'), + ); + process.exit(1); + } + } catch (error) { + console.error( + chalk.red('Login failed:'), + error instanceof Error ? error.message : error, + ); + process.exit(1); + } + } +} diff --git a/packages/twenty-sdk/src/cli/commands/auth/auth-logout.ts b/packages/twenty-sdk/src/cli/commands/auth/auth-logout.ts new file mode 100644 index 0000000000..b413c856ac --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/auth/auth-logout.ts @@ -0,0 +1,24 @@ +import chalk from 'chalk'; +import { ConfigService } from '@/cli/services/config.service'; + +export class AuthLogoutCommand { + private configService = new ConfigService(); + + async execute(): Promise { + try { + await this.configService.clearConfig(); + const activeWorkspace = ConfigService.getActiveWorkspace(); + console.log( + chalk.green( + `✓ Successfully logged out (workspace: ${activeWorkspace})`, + ), + ); + } catch (error) { + console.error( + chalk.red('Logout failed:'), + error instanceof Error ? error.message : error, + ); + process.exit(1); + } + } +} diff --git a/packages/twenty-sdk/src/cli/commands/auth/auth-status.ts b/packages/twenty-sdk/src/cli/commands/auth/auth-status.ts new file mode 100644 index 0000000000..c8d1b18468 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/auth/auth-status.ts @@ -0,0 +1,37 @@ +import chalk from 'chalk'; +import { ApiService } from '@/cli/services/api.service'; +import { ConfigService } from '@/cli/services/config.service'; + +export class AuthStatusCommand { + private configService = new ConfigService(); + private apiService = new ApiService(); + + async execute(): Promise { + try { + const activeWorkspace = ConfigService.getActiveWorkspace(); + const config = await this.configService.getConfig(); + + console.log(chalk.blue('Authentication Status:')); + console.log(`Workspace: ${activeWorkspace}`); + console.log(`API URL: ${config.apiUrl}`); + console.log( + `API Key: ${config.apiKey ? '***' + config.apiKey.slice(-4) : 'Not set'}`, + ); + + if (config.apiKey) { + const isValid = await this.apiService.validateAuth(); + console.log( + `Status: ${isValid ? chalk.green('✓ Valid') : chalk.red('✗ Invalid')}`, + ); + } else { + console.log(`Status: ${chalk.yellow('⚠ Not authenticated')}`); + } + } catch (error) { + console.error( + chalk.red('Status check failed:'), + error instanceof Error ? error.message : error, + ); + process.exit(1); + } + } +} diff --git a/packages/twenty-sdk/src/cli/commands/auth/auth-switch.ts b/packages/twenty-sdk/src/cli/commands/auth/auth-switch.ts new file mode 100644 index 0000000000..d98739f3e0 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/auth/auth-switch.ts @@ -0,0 +1,8 @@ +// Placeholder for auth switch command +// TODO: Implement workspace switching functionality + +export class AuthSwitchCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/auth/index.ts b/packages/twenty-sdk/src/cli/commands/auth/index.ts new file mode 100644 index 0000000000..9ff7381041 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/auth/index.ts @@ -0,0 +1,4 @@ +export { AuthLoginCommand } from './auth-login'; +export { AuthLogoutCommand } from './auth-logout'; +export { AuthStatusCommand } from './auth-status'; +export { AuthSwitchCommand } from './auth-switch'; diff --git a/packages/twenty-sdk/src/cli/commands/entity/entity-add.ts b/packages/twenty-sdk/src/cli/commands/entity/entity-add.ts new file mode 100644 index 0000000000..851c934096 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/entity/entity-add.ts @@ -0,0 +1,8 @@ +// Placeholder for entity add command +// TODO: Implement entity add functionality + +export class EntityAddCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/entity/index.ts b/packages/twenty-sdk/src/cli/commands/entity/index.ts new file mode 100644 index 0000000000..3df3d776b2 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/entity/index.ts @@ -0,0 +1 @@ +export { EntityAddCommand } from './entity-add'; diff --git a/packages/twenty-sdk/src/cli/commands/instance/index.ts b/packages/twenty-sdk/src/cli/commands/instance/index.ts new file mode 100644 index 0000000000..923ce735cd --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/instance/index.ts @@ -0,0 +1,3 @@ +export { InstanceSetupCommand } from './instance-setup'; +export { InstanceUpgradeCommand } from './instance-upgrade'; +export { InstanceRunCommand } from './instance-run'; diff --git a/packages/twenty-sdk/src/cli/commands/instance/instance-run.ts b/packages/twenty-sdk/src/cli/commands/instance/instance-run.ts new file mode 100644 index 0000000000..f197ccaefc --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/instance/instance-run.ts @@ -0,0 +1,8 @@ +// Placeholder for instance run command +// TODO: Implement instance run functionality + +export class InstanceRunCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/instance/instance-setup.ts b/packages/twenty-sdk/src/cli/commands/instance/instance-setup.ts new file mode 100644 index 0000000000..6a55397e88 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/instance/instance-setup.ts @@ -0,0 +1,8 @@ +// Placeholder for instance setup command +// TODO: Implement instance setup functionality + +export class InstanceSetupCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/instance/instance-upgrade.ts b/packages/twenty-sdk/src/cli/commands/instance/instance-upgrade.ts new file mode 100644 index 0000000000..3efa9fa292 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/instance/instance-upgrade.ts @@ -0,0 +1,8 @@ +// Placeholder for instance upgrade command +// TODO: Implement instance upgrade functionality + +export class InstanceUpgradeCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +} diff --git a/packages/twenty-sdk/src/cli/commands/sdk/index.ts b/packages/twenty-sdk/src/cli/commands/sdk/index.ts new file mode 100644 index 0000000000..300e2bd4e3 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/sdk/index.ts @@ -0,0 +1 @@ +export { SdkGenerateCommand } from './sdk-generate'; diff --git a/packages/twenty-sdk/src/cli/commands/sdk/sdk-generate.ts b/packages/twenty-sdk/src/cli/commands/sdk/sdk-generate.ts new file mode 100644 index 0000000000..20ca9bc860 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/sdk/sdk-generate.ts @@ -0,0 +1,8 @@ +// Placeholder for sdk generate command +// TODO: Implement SDK generation functionality + +export class SdkGenerateCommand { + async execute(): Promise { + throw new Error('Not implemented'); + } +}