Twenty SDK command renaming and dev mode (#17245)

## Description

This PR improves the developer experience for the `twenty-sdk` and
`create-twenty-app` packages by reorganizing commands, adding
development tooling, and improving documentation.

## Changes

### twenty-sdk

#### Command Refactoring
- **Renamed `app-watch` → `app-dev`**: Renamed `AppWatchCommand` to
`AppDevCommand` and moved to `app-dev.ts` for consistent naming with the
CLI command `app:dev`
- **Moved `app-add` → `entity-add`**: Relocated entity creation logic
from `app/app-add.ts` to `entity/entity-add.ts` and renamed
`AppAddCommand` to `EntityAddCommand` for better separation of concerns

#### Development Tooling
- **Added `dev` target**: New Nx target `npx nx run twenty-sdk:dev` that
runs the build in watch mode for faster development iteration

### create-twenty-app

#### Improved Scaffolded Project
- **Enhanced base-application README** with:
  - Updated Getting Started to recommend `yarn dev` for development
  - Added "Available Commands" section listing all available scripts
  
#### Better Onboarding
- **Improved success message** after app creation with formatted next
steps:
This commit is contained in:
Charles Bochet
2026-01-19 16:02:12 +01:00
committed by GitHub
parent 3958664e87
commit 905898d109
11 changed files with 367 additions and 291 deletions
+15 -15
View File
@@ -28,29 +28,29 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Get Help
yarn run help
# Authenticate using your API key (you'll be prompted)
yarn auth
yarn auth:login
# Add a new entity to your application (guided)
yarn create-entity
yarn app:add
# Generate a typed Twenty client and workspace entity types
yarn generate
yarn app:generate
# Start dev mode: automatically syncs local changes to your workspace
yarn dev
yarn app:dev
# Or run a onetime sync
yarn sync
yarn app:sync
# Watch your application's functions logs
yarn logs
yarn app:logs
# Uninstall the application from the current workspace
yarn uninstall
# Display commands' help
yarn help
yarn app:uninstall
```
## What gets scaffolded
@@ -60,9 +60,9 @@ yarn help
- Example placeholders to help you add entities, actions, and sync logic
## Next steps
- Explore the generated project and add your first entity with `yarn create-entity`.
- Keep your types uptodate using `yarn generate`.
- Use `yarn dev` while you iterate to see changes instantly in your workspace.
- Explore the generated project and add your first entity with `yarn app:add`.
- Keep your types uptodate using `yarn app:generate`.
- Use `yarn app:dev` while you iterate to see changes instantly in your workspace.
## Publish your application
@@ -90,8 +90,8 @@ git push
Our team reviews contributions for quality, security, and reusability before merging.
## Troubleshooting
- Auth prompts not appearing: run `yarn auth` again and verify the API key permissions.
- Types not generated: ensure `yarn generate` runs without errors, then restart `yarn dev`.
- Auth prompts not appearing: run `yarn auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn app:generate` runs without errors, then restart `yarn app:dev`.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
@@ -5,17 +5,40 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn auth
yarn auth:login
```
Then, install this app to your workspace:
Then, start development mode to sync your app and watch for changes:
```bash
yarn sync
yarn app:dev
```
Or run a one-time sync:
```bash
yarn app:sync
```
Open your Twenty instance and go to `/settings/applications` section to see the result.
## Available Commands
```bash
# Authentication
yarn auth:login # Authenticate with Twenty
yarn auth:logout # Remove credentials
yarn auth:status # Check auth status
# Application
yarn app:dev # Start dev mode (sync + watch)
yarn app:sync # One-time sync
yarn app:add # Add a new entity (function, object, role)
yarn app:generate # Generate typed Twenty client
yarn app:logs # Stream function logs
yarn app:uninstall # Uninstall app from workspace
```
## Learn More
To learn more about Twenty applications, take a look at the following resources:
@@ -1,12 +1,12 @@
import { copyBaseApplicationProject } from '@/utils/app-template';
import { convertToLabel } from '@/utils/convert-to-label';
import { install } from '@/utils/install';
import { tryGitInit } from '@/utils/try-git-init';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import * as path from 'path';
import { copyBaseApplicationProject } from '@/utils/app-template';
import kebabCase from 'lodash.kebabcase';
import { convertToLabel } from '@/utils/convert-to-label';
import { tryGitInit } from '@/utils/try-git-init';
import { install } from '@/utils/install';
import * as path from 'path';
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
@@ -119,10 +119,13 @@ export class CreateAppCommand {
}
private logSuccess(appDirectory: string): void {
const dirName = appDirectory.split('/').reverse()[0] ?? '';
console.log(chalk.green('✅ Application created!'));
console.log('');
console.log(chalk.blue('Next steps:'));
console.log(`cd ${appDirectory.split('/').reverse()[0] ?? ''}`);
console.log('yarn auth');
console.log(chalk.gray(` cd ${dirName}`));
console.log(chalk.gray(' yarn auth:login # Authenticate with Twenty'));
console.log(chalk.gray(' yarn app:dev # Start dev mode'));
}
}
@@ -68,8 +68,8 @@ describe('copyBaseApplicationProject', () => {
expect(packageJson.name).toBe('my-test-app');
expect(packageJson.version).toBe('0.1.0');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.3.1');
expect(packageJson.scripts.sync).toBe('twenty app sync');
expect(packageJson.scripts.dev).toBe('twenty app dev');
expect(packageJson.scripts['app:sync']).toBe('twenty app:sync');
expect(packageJson.scripts['app:dev']).toBe('twenty app:dev');
});
it('should create .gitignore file', async () => {
@@ -156,16 +156,18 @@ const createPackageJson = async ({
},
packageManager: 'yarn@4.9.2',
scripts: {
'create-entity': 'twenty app add',
dev: 'twenty app dev',
generate: 'twenty app generate',
sync: 'twenty app sync',
logs: 'twenty app logs',
uninstall: 'twenty app uninstall',
'auth:login': 'twenty auth:login',
'auth:logout': 'twenty auth:logout',
'auth:status': 'twenty auth:status',
'app:dev': 'twenty app:dev',
'app:sync': 'twenty app:sync',
'app:add': 'twenty app:add',
'app:generate': 'twenty app:generate',
'app:logs': 'twenty app:logs',
'app:uninstall': 'twenty app:uninstall',
help: 'twenty help',
auth: 'twenty auth login',
lint: 'eslint',
'lint-fix': 'eslint --fix',
'lint:fix': 'eslint --fix',
},
dependencies: {
'twenty-sdk': '0.3.1',
+74 -29
View File
@@ -58,51 +58,51 @@ Commands:
Authenticate the CLI against your Twenty workspace.
- `twenty auth login` — Authenticate with Twenty.
- `twenty auth:login` — Authenticate with Twenty.
- Options:
- `--api-key <key>`: API key for authentication.
- `--api-url <url>`: Twenty API URL (defaults to your current profile's value or `http://localhost:3000`).
- Behavior: Prompts for any missing values, persists them to the active workspace profile, and validates the credentials.
- `twenty auth logout` — Remove authentication credentials for the active workspace profile.
- `twenty auth:logout` — Remove authentication credentials for the active workspace profile.
- `twenty auth status` — Print the current authentication status (API URL, masked API key, validity).
- `twenty auth:status` — Print the current authentication status (API URL, masked API key, validity).
Examples:
```bash
# Login interactively (recommended)
twenty auth login
twenty auth:login
# Provide values in flags
twenty auth login --api-key $TWENTY_API_KEY --api-url https://api.twenty.com
twenty auth:login --api-key $TWENTY_API_KEY --api-url https://api.twenty.com
# Login interactively for a specific workspace profile
twenty auth login --workspace my-custom-workspace
twenty auth:login --workspace my-custom-workspace
# Check status
twenty auth status
twenty auth:status
# Logout current profile
twenty auth logout
twenty auth:logout
```
### App
Application development commands.
- `twenty app sync [appPath]` — One-time sync of the application to your Twenty workspace.
- Behavior: Compute your application's manifest and send it to your workspace to sync your application
- `twenty app:sync [appPath]` — One-time sync of the application to your Twenty workspace.
- Behavior: Compute your application's manifest and send it to your workspace to sync your application
- `twenty app dev [appPath]`Watch and sync local application changes.
- `twenty app:dev [appPath]`Start development mode: sync local application changes.
- Options:
- `-d, --debounce <ms>`: Debounce delay in milliseconds (default: `1000`).
- Behavior: Performs an initial sync, then watches the directory for changes and re-syncs after debounced edits. Press Ctrl+C to stop.
- `twenty app uninstall [appPath]` — Uninstall the application from the current workspace.
- Note: `twenty app delete` exists as a hidden alias for backward compatibility.
- `twenty app:uninstall [appPath]` — Uninstall the application from the current workspace.
- Note: `twenty app:delete` exists as a hidden alias for backward compatibility.
- `twenty app add [entityType]` — Add a new entity to your application.
- `twenty app:add [entityType]` — Add a new entity to your application.
- Arguments:
- `entityType`: one of `function` or `object`. If omitted, an interactive prompt is shown.
- Options:
@@ -111,9 +111,9 @@ Application development commands.
- `object`: prompts for singular/plural names and labels, then creates a new object definition file.
- `function`: prompts for a name and scaffolds a serverless function file.
- `twenty app generate [appPath]` — Generate the typed Twenty client for your application.
- `twenty app:generate [appPath]` — Generate the typed Twenty client for your application.
- `twenty app logs [appPath]` — Stream application function logs.
- `twenty app:logs [appPath]` — Stream application function logs.
- Options:
- `-u, --functionUniversalIdentifier <id>`: Only show logs for a specific function universal ID.
- `-n, --functionName <name>`: Only show logs for a specific function name.
@@ -122,28 +122,28 @@ Examples:
```bash
# Start dev mode with default debounce
twenty app dev
twenty app:dev
# Start dev mode with custom workspace profile
twenty app dev --workspace my-custom-workspace
twenty app:dev --workspace my-custom-workspace
# Dev mode with custom debounce
twenty app dev --debounce 1500
twenty app:dev --debounce 1500
# One-time sync of the current directory
twenty app sync
twenty app:sync
# Add a new object interactively
twenty app add
twenty app:add
# Generate client types
twenty app generate
twenty app:generate
# Watch all function logs
twenty app logs
twenty app:logs
# Watch logs for a specific function by name
twenty app logs -n my-function
twenty app:logs -n my-function
```
## Configuration
@@ -173,15 +173,60 @@ Example configuration file:
Notes:
- If a profile is missing, `apiUrl` defaults to `http://localhost:3000` until set.
- `twenty auth login` writes the `apiUrl` and `apiKey` for the default profile.
- `twenty auth login --workspace custom-workspace` writes the `apiUrl` and `apiKey` for a custom `custom-workspace` profile.
- `twenty auth:login` writes the `apiUrl` and `apiKey` for the default profile.
- `twenty auth:login --workspace custom-workspace` writes the `apiUrl` and `apiKey` for a custom `custom-workspace` profile.
## Troubleshooting
- Auth errors: run `twenty auth login` again and ensure the API key has the required permissions.
- Typings out of date: run `twenty app generate` to refresh the client and types.
- Not seeing changes in dev: make sure dev mode is running (`twenty app dev`).
- Auth errors: run `twenty auth:login` again and ensure the API key has the required permissions.
- Typings out of date: run `twenty app:generate` to refresh the client and types.
- Not seeing changes in dev: make sure dev mode is running (`twenty app:dev`).
## Contributing
### Development Setup
To contribute to the twenty-sdk package, clone the repository and install dependencies:
```bash
git clone https://github.com/twentyhq/twenty.git
cd twenty
yarn install
```
### Development Mode
Run the SDK build in watch mode to automatically rebuild on file changes:
```bash
npx nx run twenty-sdk:dev
```
This will watch for changes and rebuild the `dist` folder automatically.
### Production Build
Build the SDK for production:
```bash
npx nx run twenty-sdk:build
```
### Running the CLI Locally
After building, you can run the CLI directly:
```bash
npx nx run twenty-sdk:start -- <command>
# Example: npx nx run twenty-sdk:start -- auth:status
```
Or run the built CLI directly:
```bash
node packages/twenty-sdk/dist/cli.cjs <command>
```
### Resources
- See our [GitHub](https://github.com/twentyhq/twenty)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
+11
View File
@@ -16,6 +16,17 @@
"{projectRoot}/dist"
]
},
"dev": {
"executor": "nx:run-commands",
"dependsOn": [
"generateBarrels",
"^build"
],
"options": {
"cwd": "packages/twenty-sdk",
"command": "npx rimraf dist && npx vite build --watch"
}
},
"start": {
"executor": "nx:run-commands",
"dependsOn": [
@@ -1,20 +1,20 @@
import { formatPath } from '@/cli/utilities/file/utils/file-path';
import chalk from 'chalk';
import type { Command } from 'commander';
import {
AppAddCommand,
isSyncableEntity,
SyncableEntity,
} from './app/app-add';
import { AppBuildCommand } from './app/app-build';
import { AppDevCommand } from './app/app-dev';
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';
import {
EntityAddCommand,
isSyncableEntity,
SyncableEntity,
} from './entity/entity-add';
export const registerCommands = (program: Command): void => {
// Auth commands
@@ -46,20 +46,20 @@ export const registerCommands = (program: Command): void => {
});
// App commands
const watchCommand = new AppWatchCommand();
const devCommand = new AppDevCommand();
const syncCommand = new AppSyncCommand();
const uninstallCommand = new AppUninstallCommand();
const addCommand = new AppAddCommand();
const addCommand = new EntityAddCommand();
const generateCommand = new AppGenerateCommand();
const logsCommand = new AppLogsCommand();
const buildCommand = new AppBuildCommand();
program
.command('app:dev [appPath]')
.description('Watch and sync local application changes')
.description('Start development mode: sync local application changes')
.option('-d, --debounce <ms>', 'Debounce delay in milliseconds', '1000')
.action(async (appPath, options) => {
await watchCommand.execute({
await devCommand.execute({
...options,
appPath: formatPath(appPath),
});
@@ -1,212 +0,0 @@
import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import { join } from 'path';
import camelcase from 'lodash.camelcase';
import kebabcase from 'lodash.kebabcase';
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
import { getNewObjectFileContent } from '@/cli/utilities/entity/utils/entity-object-template';
import { getFunctionBaseFile } from '@/cli/utilities/entity/utils/entity-function-template';
import { getRoleBaseFile } from '@/cli/utilities/entity/utils/entity-role-template';
import { convertToLabel } from '@/cli/utilities/entity/utils/entity-label';
const APP_FOLDER = 'src/app';
export enum SyncableEntity {
AGENT = 'agent',
OBJECT = 'object',
FUNCTION = 'function',
ROLE = 'role',
}
export const isSyncableEntity = (value: string): value is SyncableEntity => {
return Object.values(SyncableEntity).includes(value as SyncableEntity);
};
export class AppAddCommand {
async execute(entityType?: SyncableEntity, path?: string): Promise<void> {
try {
// Default to src/app/ folder, allow override with path parameter
const appPath = path
? join(CURRENT_EXECUTION_DIRECTORY, path)
: join(CURRENT_EXECUTION_DIRECTORY, APP_FOLDER);
await fs.ensureDir(appPath);
const entity = entityType ?? (await this.getEntity());
if (entity === SyncableEntity.OBJECT) {
const entityData = await this.getObjectData();
const name = entityData.nameSingular;
// Use *.object.ts naming convention
const objectFileName = `${camelcase(name)}.object.ts`;
const decoratedObject = getNewObjectFileContent({
data: entityData,
name,
});
const filePath = join(appPath, objectFileName);
await fs.writeFile(filePath, decoratedObject);
console.log(
chalk.green(`✓ Created object:`),
chalk.cyan(filePath.replace(CURRENT_EXECUTION_DIRECTORY + '/', '')),
);
return;
}
if (entity === SyncableEntity.FUNCTION) {
const entityName = await this.getEntityName(entity);
// Use *.function.ts naming convention
const functionFileName = `${kebabcase(entityName)}.function.ts`;
const decoratedServerlessFunction = getFunctionBaseFile({
name: entityName,
});
const filePath = join(appPath, functionFileName);
await fs.writeFile(filePath, decoratedServerlessFunction);
console.log(
chalk.green(`✓ Created function:`),
chalk.cyan(filePath.replace(CURRENT_EXECUTION_DIRECTORY + '/', '')),
);
return;
}
if (entity === SyncableEntity.ROLE) {
const entityName = await this.getEntityName(entity);
// Use *.role.ts naming convention
const roleFileName = `${kebabcase(entityName)}.role.ts`;
const roleFileContent = getRoleBaseFile({
name: entityName,
});
const filePath = join(appPath, roleFileName);
await fs.writeFile(filePath, roleFileContent);
console.log(
chalk.green(`✓ Created role:`),
chalk.cyan(filePath.replace(CURRENT_EXECUTION_DIRECTORY + '/', '')),
);
return;
}
} catch (error) {
console.error(
chalk.red(`Add new entity failed:`),
error instanceof Error ? error.message : error,
);
process.exit(1);
}
}
private async getEntity() {
const { entity } = await inquirer.prompt<{ entity: SyncableEntity }>([
{
type: 'select',
name: 'entity',
message: `What entity do you want to create?`,
default: '',
choices: [SyncableEntity.FUNCTION, SyncableEntity.OBJECT, SyncableEntity.ROLE],
},
]);
return entity;
}
private async getEntityName(entity: SyncableEntity) {
const { name } = await inquirer.prompt<{ name: string }>([
{
type: 'input',
name: 'name',
message: `Enter a name for your new ${entity}:`,
default: '',
validate: (input) => {
if (input.length === 0) {
return `${entity} name is required`;
}
if (!/^[a-z0-9-]+$/.test(input)) {
return 'Name must contain only lowercase letters, numbers, and hyphens';
}
return true;
},
},
]);
return name;
}
private async getObjectData() {
return inquirer.prompt([
{
type: 'input',
name: 'nameSingular',
message: 'Enter a name singular for your object (eg: company):',
default: '',
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
{
type: 'input',
name: 'namePlural',
message: 'Enter a name plural for your object (eg: companies):',
default: '',
validate: (input: string, answers?: any) => {
if (input.trim() === answers?.nameSingular.trim()) {
return 'Name plural must be different from name singular';
}
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
{
type: 'input',
name: 'labelSingular',
message: 'Enter a label singular for your object:',
default: (answers: any) => {
return convertToLabel(answers.nameSingular);
},
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
{
type: 'input',
name: 'labelPlural',
message: 'Enter a label plural for your object:',
default: (answers: any) => {
return convertToLabel(answers.namePlural);
},
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
]);
}
}
@@ -10,7 +10,7 @@ import { loadManifest } from '@/cli/utilities/manifest/utils/manifest-load';
import chalk from 'chalk';
import * as chokidar from 'chokidar';
export class AppWatchCommand {
export class AppDevCommand {
private apiService = new ApiService();
async execute(options: {
@@ -1,8 +1,212 @@
// Placeholder for entity add command
// TODO: Implement entity add functionality
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
import { getFunctionBaseFile } from '@/cli/utilities/entity/utils/entity-function-template';
import { convertToLabel } from '@/cli/utilities/entity/utils/entity-label';
import { getNewObjectFileContent } from '@/cli/utilities/entity/utils/entity-object-template';
import { getRoleBaseFile } from '@/cli/utilities/entity/utils/entity-role-template';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import camelcase from 'lodash.camelcase';
import kebabcase from 'lodash.kebabcase';
import { join } from 'path';
const APP_FOLDER = 'src/app';
export enum SyncableEntity {
AGENT = 'agent',
OBJECT = 'object',
FUNCTION = 'function',
ROLE = 'role',
}
export const isSyncableEntity = (value: string): value is SyncableEntity => {
return Object.values(SyncableEntity).includes(value as SyncableEntity);
};
export class EntityAddCommand {
async execute(): Promise<void> {
throw new Error('Not implemented');
async execute(entityType?: SyncableEntity, path?: string): Promise<void> {
try {
// Default to src/app/ folder, allow override with path parameter
const appPath = path
? join(CURRENT_EXECUTION_DIRECTORY, path)
: join(CURRENT_EXECUTION_DIRECTORY, APP_FOLDER);
await fs.ensureDir(appPath);
const entity = entityType ?? (await this.getEntity());
if (entity === SyncableEntity.OBJECT) {
const entityData = await this.getObjectData();
const name = entityData.nameSingular;
// Use *.object.ts naming convention
const objectFileName = `${camelcase(name)}.object.ts`;
const decoratedObject = getNewObjectFileContent({
data: entityData,
name,
});
const filePath = join(appPath, objectFileName);
await fs.writeFile(filePath, decoratedObject);
console.log(
chalk.green(`✓ Created object:`),
chalk.cyan(filePath.replace(CURRENT_EXECUTION_DIRECTORY + '/', '')),
);
return;
}
if (entity === SyncableEntity.FUNCTION) {
const entityName = await this.getEntityName(entity);
// Use *.function.ts naming convention
const functionFileName = `${kebabcase(entityName)}.function.ts`;
const decoratedServerlessFunction = getFunctionBaseFile({
name: entityName,
});
const filePath = join(appPath, functionFileName);
await fs.writeFile(filePath, decoratedServerlessFunction);
console.log(
chalk.green(`✓ Created function:`),
chalk.cyan(filePath.replace(CURRENT_EXECUTION_DIRECTORY + '/', '')),
);
return;
}
if (entity === SyncableEntity.ROLE) {
const entityName = await this.getEntityName(entity);
// Use *.role.ts naming convention
const roleFileName = `${kebabcase(entityName)}.role.ts`;
const roleFileContent = getRoleBaseFile({
name: entityName,
});
const filePath = join(appPath, roleFileName);
await fs.writeFile(filePath, roleFileContent);
console.log(
chalk.green(`✓ Created role:`),
chalk.cyan(filePath.replace(CURRENT_EXECUTION_DIRECTORY + '/', '')),
);
return;
}
} catch (error) {
console.error(
chalk.red(`Add new entity failed:`),
error instanceof Error ? error.message : error,
);
process.exit(1);
}
}
private async getEntity() {
const { entity } = await inquirer.prompt<{ entity: SyncableEntity }>([
{
type: 'select',
name: 'entity',
message: `What entity do you want to create?`,
default: '',
choices: [SyncableEntity.FUNCTION, SyncableEntity.OBJECT, SyncableEntity.ROLE],
},
]);
return entity;
}
private async getEntityName(entity: SyncableEntity) {
const { name } = await inquirer.prompt<{ name: string }>([
{
type: 'input',
name: 'name',
message: `Enter a name for your new ${entity}:`,
default: '',
validate: (input) => {
if (input.length === 0) {
return `${entity} name is required`;
}
if (!/^[a-z0-9-]+$/.test(input)) {
return 'Name must contain only lowercase letters, numbers, and hyphens';
}
return true;
},
},
]);
return name;
}
private async getObjectData() {
return inquirer.prompt([
{
type: 'input',
name: 'nameSingular',
message: 'Enter a name singular for your object (eg: company):',
default: '',
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
{
type: 'input',
name: 'namePlural',
message: 'Enter a name plural for your object (eg: companies):',
default: '',
validate: (input: string, answers?: any) => {
if (input.trim() === answers?.nameSingular.trim()) {
return 'Name plural must be different from name singular';
}
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
{
type: 'input',
name: 'labelSingular',
message: 'Enter a label singular for your object:',
default: (answers: any) => {
return convertToLabel(answers.nameSingular);
},
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
{
type: 'input',
name: 'labelPlural',
message: 'Enter a label plural for your object:',
default: (answers: any) => {
return convertToLabel(answers.namePlural);
},
validate: (input: string) => {
if (!input || input.trim().length === 0) {
return 'Please enter a non empty string';
}
return true;
},
},
]);
}
}