Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/ci-create-app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: CI Create App

on:
push:
branches:
- main

pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
changed-files-check:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/create-twenty-app/**
packages/twenty-server/**
create-app-test:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
matrix:
task: [lint, typecheck, test, build]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Run ${{ matrix.task }} task
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:create-app
tasks: ${{ matrix.task }}
ci-create-app-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, create-app-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
20 changes: 10 additions & 10 deletions .github/workflows/ci-cli.yaml → .github/workflows/ci-sdk.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI CLI
name: CI SDK

on:
push:
Expand All @@ -19,9 +19,9 @@ jobs:
uses: ./.github/workflows/changed-files.yaml
with:
files: |
packages/twenty-cli/**
packages/twenty-sdk/**
packages/twenty-server/**
cli-test:
sdk-test:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
Expand All @@ -43,12 +43,12 @@ jobs:
- name: Run ${{ matrix.task }} task
uses: ./.github/workflows/actions/nx-affected
with:
tag: scope:cli
tag: scope:sdk
tasks: ${{ matrix.task }}
cli-e2e-test:
sdk-e2e-test:
timeout-minutes: 30
runs-on: depot-ubuntu-24.04-8
needs: [changed-files-check, cli-test]
needs: [changed-files-check, sdk-test]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
postgres:
Expand Down Expand Up @@ -90,13 +90,13 @@ jobs:
- name: Server / Create Test DB
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
- name: CLI / Run E2E Tests
run: npx nx test:e2e twenty-cli
ci-cli-status-check:
- name: SDK / Run E2E Tests
run: npx nx test:e2e twenty-sdk
ci-sdk-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs: [changed-files-check, cli-test, cli-e2e-test]
needs: [changed-files-check, sdk-test, sdk-e2e-test]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
"packages/twenty-sdk",
"packages/twenty-apps",
"packages/twenty-cli",
"packages/create-twenty-app",
"tools/eslint-rules"
]
}
Expand Down
91 changes: 91 additions & 0 deletions packages/create-twenty-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<div align="center">
<a href="https://twenty.com">
<picture>
<img alt="Twenty logo" src="https://raw.githubusercontent.com/twentyhq/twenty/2f25922f4cd5bd61e1427c57c4f8ea224e1d552c/packages/twenty-website/public/images/core/logo.svg" height="128">
</picture>
</a>
<h1>Create Twenty App</h1>

<a href="https://www.npmjs.com/package/create-twenty-app"><img alt="NPM version" src="https://img.shields.io/npm/v/create-twenty-app.svg?style=for-the-badge&labelColor=000000"></a>
<a href="https://github.com/twentyhq/twenty/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/npm/l/next.svg?style=for-the-badge&labelColor=000000"></a>
<a href="https://discord.gg/cx5n4Jzs57"><img alt="Join the community on Discord" src="https://img.shields.io/badge/Join%20the%20community-blueviolet.svg?style=for-the-badge&logo=Twenty&labelColor=000000&logoWidth=20"></a>

</div>

Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a ready‑to‑run project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).

- Zero‑config project bootstrap
- Preconfigured scripts for auth, generate, dev sync, one‑off sync, uninstall
- Strong TypeScript support and typed client generation

## Prerequisites
- Node.js 18+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)

## Quick start
```bash
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app

# Authenticate using your API key (you'll be prompted)
yarn auth

# Add a new entity to your application (guided)
yarn create-entity

# Generate a typed Twenty client and workspace entity types
yarn generate

# Start dev mode: automatically syncs local changes to your workspace
yarn dev

# Or run a one‑time sync
yarn sync

# Uninstall the application from the current workspace
yarn uninstall
```

## What gets scaffolded
- A minimal app structure ready for Twenty
- TypeScript configuration
- Prewired scripts that wrap the `twenty` CLI from twenty-sdk
- 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 up‑to‑date using `yarn generate`.
- Use `yarn dev` while you iterate to see changes instantly in your workspace.


## Publish your application
Applications are currently stored in `twenty/packages/twenty-apps`.

You can share your application with all Twenty users:

```bash
# pull the Twenty project
git clone https://github.com/twentyhq/twenty.git
cd twenty

# create a new branch
git checkout -b feature/my-awesome-app
```

- Copy your app folder into `twenty/packages/twenty-apps`.
- Commit your changes and open a pull request on https://github.com/twentyhq/twenty

```bash
git commit -m "Add new application"
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 re‑start `yarn dev`.

## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
46 changes: 46 additions & 0 deletions packages/create-twenty-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "create-twenty-app",
"version": "0.1.0",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.js",
"bin": "dist/cli.js",
"files": [
"dist/**/*"
],
"scripts": {
"build": "echo 'use npx nx build'",
"dev": "tsx src/cli.ts",
"start": "node dist/cli.js"
},
"keywords": [
"twenty",
"cli",
"crm",
"application",
"development"
],
"license": "AGPL-3.0",
"dependencies": {
"@genql/cli": "^3.0.3",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"fs-extra": "^11.2.0",
"inquirer": "^10.0.0",
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "^4.4.0",
"uuid": "^13.0.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.0",
"@types/inquirer": "^9.0.0",
"@types/lodash.camelcase": "^4.3.7",
"@types/lodash.kebabcase": "^4.1.7",
"@types/lodash.startcase": "^4",
"@types/node": "^20.0.0"
},
"engines": {
"node": "^24.5.0",
"yarn": "^4.0.2"
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
{
"name": "twenty-cli",
"name": "create-twenty-app",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"tags": ["scope:cli"],
"projectType": "library",
"tags": ["scope:create-app"],
"targets": {
"before-build": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "packages/twenty-cli",
"commands": ["rimraf dist", "tsc --project tsconfig.lib.json"]
},
"dependsOn": ["^build", "typecheck"]
},
"build": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "packages/twenty-cli",
"commands": [
"cp -R src/constants/base-application-project dist/constants"
]
"cwd": "packages/create-twenty-app",
"commands": ["rimraf dist", "tsc --project tsconfig.json"]
},
"dependsOn": ["before-build"]
"dependsOn": ["^build", "typecheck"]
},
"dev": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"options": {
"cwd": "packages/twenty-cli",
"cwd": "packages/create-twenty-app",
"command": "tsx src/cli.ts"
}
},
"start": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
"options": {
"cwd": "packages/twenty-cli",
"cwd": "packages/create-twenty-app",
"command": "node dist/cli.js"
}
},
Expand Down Expand Up @@ -67,28 +56,6 @@
"watchAll": false
}
}
},
"test:e2e": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"cwd": "packages/twenty-cli",
"commands": [
"npx wait-on http://localhost:3000/healthz --timeout 600000 --interval 1000 --log && NODE_ENV=test npx jest --config ./jest.e2e.config.ts"
]
},
"parallel": false,
"dependsOn": [
"build",
{
"target": "database:reset",
"projects": "twenty-server"
},
{
"target": "start:ci-if-needed",
"projects": "twenty-server"
}
]
}
}
}
45 changes: 45 additions & 0 deletions packages/create-twenty-app/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env node
import chalk from 'chalk';
import { Command, CommanderError } from 'commander';
import { readFileSync } from 'fs';
import { join } from 'path';
import { CreateAppCommand } from './create-app.command';

const packageJson = JSON.parse(
readFileSync(join(__dirname, '../package.json'), 'utf-8'),
);

const program = new Command(packageJson.name)
.description('CLI tool to initialize a new Twenty application')
.version(
packageJson.version,
'-v, --version',
'Output the current version of create-twenty-app.',
)
.argument('[directory]')
.helpOption('-h, --help', 'Display this help message.')
.action(async (directory?: string) => {
if (directory && !/^[a-z0-9-]+$/.test(directory)) {
console.error(
chalk.red(
`Invalid directory "${directory}". Must contain only lowercase letters, numbers, and hyphens`,
),
);
process.exit(1);
}
await new CreateAppCommand().execute(directory);
});

program.exitOverride();

try {
program.parse();
} catch (error) {
if (error instanceof CommanderError) {
process.exit(error.exitCode);
}
if (error instanceof Error) {
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
}
Loading
Loading