mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
Merge twenty-cli into twenty-sdk (#16150)
- Moves twenty-cli content into twenty-sdk - add a new twenty-sdk:0.1.0 version - this new twenty-sdk exports a cli command called 'twenty' (like twenty-cli before) - deprecates twenty-cli - simplify app init command base-project - use `twenty-sdk:0.1.0` in base project - move the "twenty-sdk/application" barrel to "twenty-sdk" - add `create-twenty-app` package <img width="1512" height="919" alt="image" src="https://github.com/user-attachments/assets/007bef45-4e71-419a-9213-cebed376adbf" /> <img width="1506" height="929" alt="image" src="https://github.com/user-attachments/assets/3de2fec6-1624-4923-ae13-f4e1cf165eb5" />
This commit is contained in:
parent
3f08a0c901
commit
e498367e2f
85 changed files with 1077 additions and 1560 deletions
56
.github/workflows/ci-create-app.yaml
vendored
Normal file
56
.github/workflows/ci-create-app.yaml
vendored
Normal file
|
|
@ -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
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: CI CLI
|
||||
name: CI SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
@ -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')
|
||||
|
|
@ -231,6 +231,7 @@
|
|||
"packages/twenty-sdk",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
"packages/create-twenty-app",
|
||||
"tools/eslint-rules"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
91
packages/create-twenty-app/README.md
Normal file
91
packages/create-twenty-app/README.md
Normal file
|
|
@ -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
packages/create-twenty-app/package.json
Normal file
46
packages/create-twenty-app/package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +1,23 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
|
@ -36,7 +25,7 @@
|
|||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-cli",
|
||||
"cwd": "packages/create-twenty-app",
|
||||
"command": "node dist/cli.js"
|
||||
}
|
||||
},
|
||||
|
|
@ -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
packages/create-twenty-app/src/cli.ts
Normal file
45
packages/create-twenty-app/src/cli.ts
Normal file
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,17 @@ 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 { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { copyBaseApplicationProject } from './utils/app-template';
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { convertToLabel } from '../utils/convert-to-label';
|
||||
import { convertToLabel } from './utils/convert-to-label';
|
||||
|
||||
export class AppInitCommand {
|
||||
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
export class CreateAppCommand {
|
||||
async execute(directory?: string): Promise<void> {
|
||||
try {
|
||||
const { appName, appDisplayName, appDirectory, appDescription } =
|
||||
|
|
@ -25,7 +31,18 @@ export class AppInitCommand {
|
|||
appDirectory,
|
||||
});
|
||||
|
||||
this.logSuccess(appDirectory);
|
||||
try {
|
||||
const result = await execPromise('yarn --version', {
|
||||
cwd: appDirectory,
|
||||
});
|
||||
console.log('Installing dependencies using yarn', result.stdout);
|
||||
await execPromise('yarn', { cwd: appDirectory });
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('yarn install failed:'), error.stdout);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await this.logSuccess(appDirectory);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('Initialization failed:'),
|
||||
|
|
@ -78,8 +95,8 @@ export class AppInitCommand {
|
|||
const appDescription = description.trim();
|
||||
|
||||
const appDirectory = directory
|
||||
? path.join(process.cwd(), kebabCase(directory))
|
||||
: path.join(process.cwd(), kebabCase(appName));
|
||||
? path.join(CURRENT_EXECUTION_DIRECTORY, directory)
|
||||
: path.join(CURRENT_EXECUTION_DIRECTORY, kebabCase(appName));
|
||||
|
||||
return { appName, appDisplayName, appDirectory, appDescription };
|
||||
}
|
||||
|
|
@ -114,7 +131,7 @@ export class AppInitCommand {
|
|||
console.log(chalk.green('✅ Application created successfully!'));
|
||||
console.log('');
|
||||
console.log(chalk.blue('Next steps:'));
|
||||
console.log(` cd ${appDirectory}`);
|
||||
console.log(` cd ${appDirectory.split('/').reverse()[0] ?? ''}`);
|
||||
console.log(' twenty app dev');
|
||||
}
|
||||
}
|
||||
176
packages/create-twenty-app/src/utils/app-template.ts
Normal file
176
packages/create-twenty-app/src/utils/app-template.ts
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const copyBaseApplicationProject = async ({
|
||||
appName,
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDisplayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
await createPackageJson({ appName, appDirectory });
|
||||
|
||||
await createYarnLock(appDirectory);
|
||||
|
||||
await createYarnRc(appDirectory);
|
||||
|
||||
await createNvmRc(appDirectory);
|
||||
|
||||
await createTsConfig(appDirectory);
|
||||
|
||||
await createApplicationConfig({
|
||||
displayName: appDisplayName,
|
||||
description: appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
await createReadmeContent({
|
||||
displayName: appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
};
|
||||
|
||||
const createYarnLock = async (appDirectory: string) => {
|
||||
const yarnLockContent = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
|
||||
};
|
||||
|
||||
const createYarnRc = async (appDirectory: string) => {
|
||||
const yarnRcContent = `nodeLinker: node-modules
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, '.yarnrc.yml'), yarnRcContent);
|
||||
};
|
||||
|
||||
const createNvmRc = async (appDirectory: string) => {
|
||||
const nvmRcContent = `24.5.0
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, '.nvmrc'), nvmRcContent);
|
||||
};
|
||||
|
||||
const createTsConfig = async (appDirectory: string) => {
|
||||
const tsConfigJson = {
|
||||
compileOnSave: false,
|
||||
compilerOptions: {
|
||||
sourceMap: true,
|
||||
declaration: true,
|
||||
outDir: './dist',
|
||||
rootDir: '.',
|
||||
moduleResolution: 'node',
|
||||
allowSyntheticDefaultImports: true,
|
||||
emitDecoratorMetadata: true,
|
||||
experimentalDecorators: true,
|
||||
importHelpers: true,
|
||||
allowUnreachableCode: false,
|
||||
strictNullChecks: true,
|
||||
alwaysStrict: true,
|
||||
noImplicitAny: true,
|
||||
strictBindCallApply: false,
|
||||
target: 'es2018',
|
||||
module: 'esnext',
|
||||
lib: ['es2020', 'dom'],
|
||||
skipLibCheck: true,
|
||||
skipDefaultLibCheck: true,
|
||||
resolveJsonModule: true,
|
||||
},
|
||||
|
||||
exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts'],
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
join(appDirectory, 'tsconfig.json'),
|
||||
JSON.stringify(tsConfigJson, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
};
|
||||
|
||||
const createApplicationConfig = async ({
|
||||
displayName,
|
||||
description,
|
||||
appDirectory,
|
||||
}: {
|
||||
displayName: string;
|
||||
description?: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
const content = `import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '${v4()}',
|
||||
displayName: '${displayName}',
|
||||
description: '${description ?? ''}',
|
||||
};
|
||||
|
||||
export default config;
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'application.config.ts'), content);
|
||||
};
|
||||
|
||||
const createPackageJson = async ({
|
||||
appName,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
const packageJson = {
|
||||
name: appName,
|
||||
version: '0.0.1',
|
||||
license: 'MIT',
|
||||
engines: {
|
||||
node: '^24.5.0',
|
||||
npm: 'please-use-yarn',
|
||||
yarn: '>=4.0.2',
|
||||
},
|
||||
packageManager: 'yarn@4.9.2',
|
||||
scripts: {
|
||||
'create-entity': 'twenty app add',
|
||||
dev: 'twenty app dev',
|
||||
generate: 'twenty app generate',
|
||||
sync: 'twenty app sync',
|
||||
uninstall: 'twenty app uninstall',
|
||||
auth: 'twenty auth login',
|
||||
},
|
||||
dependencies: {
|
||||
'twenty-sdk': '0.1.0',
|
||||
},
|
||||
devDependencies: {
|
||||
'@types/node': '^24.7.2',
|
||||
typescript: '^5.9.3',
|
||||
},
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
join(appDirectory, 'package.json'),
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
};
|
||||
|
||||
const createReadmeContent = async ({
|
||||
displayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
displayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
const readmeContent = `# ${displayName}
|
||||
|
||||
${appDescription}
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, 'README.md'), readmeContent);
|
||||
};
|
||||
|
|
@ -3,19 +3,24 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"target": "es2022",
|
||||
"module": "commonjs",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"declaration": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/__tests__/**"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
# Why Twenty CLI?
|
||||
# Deprecated: twenty-cli
|
||||
|
||||
A command-line interface to easily scaffold, develop, and publish applications that extend Twenty CRM
|
||||
|
||||
## Installation
|
||||
This package is deprecated. Please install and use twenty-sdk instead:
|
||||
|
||||
```bash
|
||||
npm install -g twenty-cli
|
||||
npm uninstall twenty-cli
|
||||
npm install -g twenty-sdk
|
||||
```
|
||||
|
||||
The command name remains the same: twenty.
|
||||
|
||||
A command-line interface to easily scaffold, develop, and publish applications that extend Twenty CRM (now provided by twenty-sdk).
|
||||
|
||||
## Requirements
|
||||
- yarn >= 4.9.2
|
||||
- an `apiKey`. Go to `https://twenty.com/settings/api-webhooks` to generate one
|
||||
|
|
|
|||
5
packages/twenty-cli/deprecate.js
Executable file
5
packages/twenty-cli/deprecate.js
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env node
|
||||
const message = `\nTwenty CLI (twenty-cli) is deprecated.\n\nPlease install and use the new package instead:\n npm install -g twenty-sdk\n\nThe command name remains the same: \"twenty\".\nMore info: https://www.npmjs.com/package/twenty-sdk\n`;
|
||||
|
||||
console.error(message);
|
||||
process.exitCode = 1;
|
||||
|
|
@ -1,64 +1,10 @@
|
|||
{
|
||||
"name": "twenty-cli",
|
||||
"version": "0.2.4",
|
||||
"description": "Command-line interface for Twenty application development",
|
||||
"main": "dist/cli.js",
|
||||
"bin": {
|
||||
"twenty": "dist/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"!dist/**/*.e2e-spec.*",
|
||||
"!dist/**/__tests__/**"
|
||||
],
|
||||
"version": "0.3.0",
|
||||
"description": "[DEPRECATED] Use twenty-sdk instead: https://www.npmjs.com/package/twenty-sdk",
|
||||
"scripts": {
|
||||
"build": "echo 'use npx nx build'",
|
||||
"dev": "tsx src/cli.ts",
|
||||
"start": "node dist/cli.js"
|
||||
"start": "echo 'deprecated'"
|
||||
},
|
||||
"keywords": [
|
||||
"twenty",
|
||||
"cli",
|
||||
"crm",
|
||||
"application",
|
||||
"development"
|
||||
],
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@genql/cli": "^3.0.3",
|
||||
"ajv": "^8.12.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"axios": "^1.6.0",
|
||||
"chalk": "^5.3.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^12.0.0",
|
||||
"dotenv": "^16.4.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"graphql": "^16.8.1",
|
||||
"inquirer": "^10.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.capitalize": "^4.2.1",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"lodash.startcase": "^4.4.0",
|
||||
"typescript": "^5.9.2",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
"@types/inquirer": "^9.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/lodash.camelcase": "^4.3.7",
|
||||
"@types/lodash.capitalize": "^4",
|
||||
"@types/lodash.kebabcase": "^4.1.7",
|
||||
"@types/lodash.startcase": "^4",
|
||||
"@types/node": "^20.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"tsx": "^4.7.0",
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"yarn": "^4.0.2"
|
||||
}
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
# Set environment values for your application here.
|
||||
# Use the format: KEY=value
|
||||
#
|
||||
# These variables are automatically loaded when running your serverless functions.
|
||||
# You can access them directly in your code using:
|
||||
# const myValue = process.env.KEY;
|
||||
#
|
||||
# To make these variables available to your application, add them in application.config.ts file
|
||||
#
|
||||
# const config: ApplicationConfig = {
|
||||
# ...
|
||||
# applicationVariables: {
|
||||
# KEY: {
|
||||
# universalIdentifier: 'dedc53eb-9c12-4fe2-ba86-4a2add19d305',
|
||||
# description: 'Description',
|
||||
# isSecret: true,
|
||||
# },
|
||||
# },
|
||||
# };
|
||||
#
|
||||
# Those environment variables will be provided to your serverless
|
||||
# functions at runtime.
|
||||
#
|
||||
# Example:
|
||||
# API_TOKEN=your-api-token
|
||||
# TIMEOUT_MS=3000
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Duplicated with ./gitignore because npm publish does not include .gitignore
|
||||
# https://github.com/npm/npm/issues/3763
|
||||
|
||||
.yarn/install-state.gz
|
||||
.env
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +0,0 @@
|
|||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# {title}
|
||||
|
||||
{description}
|
||||
|
||||
## Requirements
|
||||
- twenty-cli `npm install -g twenty-cli`
|
||||
- an `apiKey`. Go to `https://twenty.com/settings/api-webhooks` to generate one
|
||||
|
||||
|
||||
## Install to your Twenty workspace
|
||||
|
||||
```bash
|
||||
twenty auth login
|
||||
twenty app sync
|
||||
```
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
.yarn/install-state.gz
|
||||
.env
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"name": "my-application",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"dependencies": {
|
||||
"twenty-sdk": "0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"allowUnreachableCode": false,
|
||||
"strictNullChecks": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
# This file is generated by running "yarn install" inside your project.
|
||||
# Manual changes might be lost - proceed with caution!
|
||||
|
||||
__metadata:
|
||||
version: 8
|
||||
cacheKey: 10c0
|
||||
|
||||
"@types/node@npm:^24.7.2":
|
||||
version: 24.9.1
|
||||
resolution: "@types/node@npm:24.9.1"
|
||||
dependencies:
|
||||
undici-types: "npm:~7.16.0"
|
||||
checksum: 10c0/c52f8168080ef9a7c3dc23d8ac6061fab5371aad89231a0f6f4c075869bc3de7e89b075b1f3e3171d9e5143d0dda1807c3dab8e32eac6d68f02e7480e7e78576
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"root-workspace-0b6124@workspace:.":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "root-workspace-0b6124@workspace:."
|
||||
dependencies:
|
||||
"@types/node": "npm:^24.7.2"
|
||||
twenty-sdk: "npm:^0.0.2"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"twenty-sdk@npm:^0.0.2":
|
||||
version: 0.0.2
|
||||
resolution: "twenty-sdk@npm:0.0.2"
|
||||
checksum: 10c0/99e6fe86059d847b548c1f03e0f0c59a4d540caf1d28dd4500f1f5f0094196985ded955801274de9e72ff03e3d1f41e9a509b4c2c5a02ffc8a027277b1e35d8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~7.16.0":
|
||||
version: 7.16.0
|
||||
resolution: "undici-types@npm:7.16.0"
|
||||
checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { join } from 'path';
|
||||
|
||||
const BASE_PATH = join(__dirname, '../constants');
|
||||
|
||||
export const BASE_APPLICATION_PROJECT_PATH = join(
|
||||
BASE_PATH,
|
||||
'base-application-project',
|
||||
);
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import { BASE_APPLICATION_PROJECT_PATH } from '../constants/constants-path';
|
||||
import { writeJsoncFile } from '../utils/jsonc-parser';
|
||||
import { join } from 'path';
|
||||
import path from 'path';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const copyBaseApplicationProject = async ({
|
||||
appName,
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDisplayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
await fs.copy(BASE_APPLICATION_PROJECT_PATH, appDirectory);
|
||||
|
||||
await fs.rename(
|
||||
join(appDirectory, 'gitignore'),
|
||||
join(appDirectory, '.gitignore'),
|
||||
);
|
||||
|
||||
await fs.copy(join(appDirectory, '.env.example'), join(appDirectory, '.env'));
|
||||
|
||||
await createBasePackageJson({
|
||||
appName,
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
await createApplicationConfig({
|
||||
displayName: appDisplayName,
|
||||
description: appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
await createReadmeContent({
|
||||
displayName: appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
};
|
||||
|
||||
const createApplicationConfig = async ({
|
||||
displayName,
|
||||
description,
|
||||
appDirectory,
|
||||
}: {
|
||||
displayName: string;
|
||||
description?: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
const content = `import { type ApplicationConfig } from 'twenty-sdk/application';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '${v4()}',
|
||||
displayName: '${displayName}',
|
||||
description: '${description ?? ''}',
|
||||
};
|
||||
|
||||
export default config;
|
||||
`;
|
||||
|
||||
await fs.writeFile(path.join(appDirectory, 'application.config.ts'), content);
|
||||
};
|
||||
|
||||
const createBasePackageJson = async ({
|
||||
appName,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
const base = JSON.parse(await readBaseApplicationProjectFile('package.json'));
|
||||
|
||||
base['universalIdentifier'] = v4();
|
||||
base['name'] = appName;
|
||||
|
||||
await writeJsoncFile(join(appDirectory, 'package.json'), base);
|
||||
};
|
||||
|
||||
const createReadmeContent = async ({
|
||||
displayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
displayName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
let readmeContent = await readBaseApplicationProjectFile('README.md');
|
||||
|
||||
readmeContent = readmeContent.replace(/\{title}/g, displayName);
|
||||
|
||||
readmeContent = readmeContent.replace(/\{description}/g, appDescription);
|
||||
|
||||
await fs.writeFile(path.join(appDirectory, 'README.md'), readmeContent);
|
||||
};
|
||||
|
||||
const readBaseApplicationProjectFile = async (fileName: string) => {
|
||||
return await fs.readFile(
|
||||
join(BASE_APPLICATION_PROJECT_PATH, fileName),
|
||||
'utf-8',
|
||||
);
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/**/__tests__/**/*.spec.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,26 @@
|
|||
# twenty-sdk
|
||||
<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>Twenty SDK</h1>
|
||||
|
||||
A lightweight TypeScript SDK for Twenty CRM.
|
||||
<a href="https://www.npmjs.com/package/twenty-sdk"><img alt="NPM version" src="https://img.shields.io/npm/v/twenty-sdk.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>
|
||||
|
||||
A CLI and SDK to develop, build, and publish applications that extend [Twenty CRM](https://twenty.com).
|
||||
|
||||
- Type‑safe client and workspace entity typings
|
||||
- Built‑in CLI for auth, generate, dev sync, one‑off sync, and uninstall
|
||||
- Works great with the scaffolder: [create-twenty-app](https://www.npmjs.com/package/create-twenty-app)
|
||||
|
||||
## Prerequisites
|
||||
- Node.js 18+ (recommended) and Yarn 4
|
||||
- A Twenty workspace and an API key. Generate one at https://app.twenty.com/settings/api-webhooks
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -10,18 +30,72 @@ npm install twenty-sdk
|
|||
yarn add twenty-sdk
|
||||
```
|
||||
|
||||
## Usage
|
||||
## Getting started
|
||||
You can either scaffold a new app or add the SDK to an existing one.
|
||||
|
||||
- Start new (recommended):
|
||||
```bash
|
||||
npx create-twenty-app@latest my-twenty-app
|
||||
cd my-twenty-app
|
||||
```
|
||||
- Existing project: install the SDK as shown above, then use the CLI below.
|
||||
|
||||
## CLI quickstart
|
||||
```bash
|
||||
# Authenticate using your API key (CLI will prompt for it)
|
||||
twenty auth login
|
||||
|
||||
# Add a new entity to your application (guided prompts)
|
||||
twenty app add
|
||||
|
||||
# Generate a typed Twenty client and TypeScript definitions for your workspace entities
|
||||
twenty app generate
|
||||
|
||||
# Start dev mode: automatically syncs changes to your workspace for instant testing
|
||||
twenty app dev
|
||||
|
||||
# One‑time sync of local changes
|
||||
twenty app sync
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
twenty app uninstall
|
||||
```
|
||||
|
||||
## Usage (SDK)
|
||||
```typescript
|
||||
// Example: import what you need from the SDK
|
||||
import { /* your exports */ } from 'twenty-sdk';
|
||||
```
|
||||
|
||||
## Development
|
||||
## Publish your application
|
||||
Applications are currently stored in [`twenty/packages/twenty-apps`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps).
|
||||
|
||||
You can share your application with all Twenty users:
|
||||
|
||||
```bash
|
||||
# Build
|
||||
npx nx build twenty-sdk
|
||||
# pull the Twenty project
|
||||
git clone https://github.com/twentyhq/twenty.git
|
||||
cd twenty
|
||||
|
||||
# Lint
|
||||
npx nx lint twenty-sdk
|
||||
# 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.
|
||||
|
||||
## 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`).
|
||||
|
||||
## Contributing
|
||||
- See our [GitHub](https://github.com/twentyhq/twenty)
|
||||
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import baseConfig from '../../eslint.config.mjs';
|
|||
export default [
|
||||
...baseConfig,
|
||||
{
|
||||
ignores: ['**/dist/**', 'vite.config.ts'],
|
||||
ignores: ['**/dist/**'],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
ignores: ['src/**/*.ts', '!src/cli/**/*.ts'],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
40
packages/twenty-sdk/jest.config.mjs
Normal file
40
packages/twenty-sdk/jest.config.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const jestConfig = {
|
||||
displayName: 'twenty-cli',
|
||||
preset: '../../jest.preset.js',
|
||||
testEnvironment: 'node',
|
||||
transformIgnorePatterns: ['../../node_modules/'],
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': [
|
||||
'@swc/jest',
|
||||
{
|
||||
jsc: {
|
||||
parser: { syntax: 'typescript', tsx: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js'],
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
coverageDirectory: './coverage',
|
||||
testMatch: [
|
||||
'<rootDir>/src/**/__tests__/**/*.(test|spec).{js,ts}',
|
||||
'<rootDir>/src/**/?(*.)(test|spec).{js,ts}',
|
||||
],
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,js}',
|
||||
'!src/**/*.d.ts',
|
||||
'!src/cli/cli.ts',
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 1,
|
||||
lines: 1,
|
||||
functions: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default jestConfig;
|
||||
|
|
@ -4,7 +4,7 @@ const jestConfig: JestConfigWithTsJest = {
|
|||
// For more information please have a look to official docs https://jestjs.io/docs/configuration/#prettierpath-string
|
||||
// Prettier v3 should be supported in jest v30 https://github.com/jestjs/jest/releases/tag/v30.0.0-alpha.1
|
||||
prettierPath: null,
|
||||
displayName: 'twenty-cli-e2e',
|
||||
displayName: 'twenty-sdk-e2e',
|
||||
silent: false,
|
||||
errorOnDeprecated: true,
|
||||
maxConcurrency: 1,
|
||||
|
|
@ -13,8 +13,8 @@ const jestConfig: JestConfigWithTsJest = {
|
|||
testEnvironment: 'node',
|
||||
testRegex: '\\.e2e-spec\\.ts$',
|
||||
modulePathIgnorePatterns: ['<rootDir>/dist'],
|
||||
globalTeardown: '<rootDir>/src/__tests__/e2e/teardown.ts',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/__tests__/e2e/setupTest.ts'],
|
||||
globalTeardown: '<rootDir>/src/cli/__tests__/e2e/teardown.ts',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/cli/__tests__/e2e/setupTest.ts'],
|
||||
testTimeout: 30000, // 30 seconds timeout for e2e tests
|
||||
maxWorkers: 1,
|
||||
transform: {
|
||||
|
|
@ -1,41 +1,69 @@
|
|||
{
|
||||
"name": "twenty-sdk",
|
||||
"version": "0.0.6",
|
||||
"version": "0.1.0",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"bin": {
|
||||
"twenty": "dist/cli/cli.js"
|
||||
},
|
||||
"keywords": [
|
||||
"twenty",
|
||||
"cli",
|
||||
"sdk",
|
||||
"crm",
|
||||
"application",
|
||||
"development"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
"application"
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build"
|
||||
"build": "vite build && tsc --project tsconfig.cli.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@genql/cli": "^3.0.3",
|
||||
"axios": "^1.6.0",
|
||||
"chalk": "^5.3.0",
|
||||
"chokidar": "^4.0.0",
|
||||
"commander": "^12.0.0",
|
||||
"dotenv": "^16.4.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"graphql": "^16.8.1",
|
||||
"inquirer": "^10.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.capitalize": "^4.2.1",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"lodash.startcase": "^4.4.0",
|
||||
"typescript": "^5.9.2",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
"@types/inquirer": "^9.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/lodash.camelcase": "^4.3.7",
|
||||
"@types/lodash.capitalize": "^4",
|
||||
"@types/lodash.kebabcase": "^4.1.7",
|
||||
"@types/lodash.startcase": "^4",
|
||||
"@types/node": "^24.0.0",
|
||||
"typescript": "5.9.2",
|
||||
"jest": "^29.5.0",
|
||||
"tsx": "^4.7.0",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-dts": "3.8.1",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"wait-on": "^7.2.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.cjs"
|
||||
},
|
||||
"./application": {
|
||||
"types": "./dist/application/index.d.ts",
|
||||
"import": "./dist/application.mjs",
|
||||
"require": "./dist/application.cjs"
|
||||
}
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"application": [
|
||||
"dist/application/index.d.ts"
|
||||
]
|
||||
}
|
||||
"*": {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,19 @@
|
|||
"^build"
|
||||
],
|
||||
"outputs": [
|
||||
"{projectRoot}/dist",
|
||||
"{projectRoot}/application/package.json",
|
||||
"{projectRoot}/application/dist"
|
||||
"{projectRoot}/dist"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-sdk",
|
||||
"command": "node dist/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"generateBarrels": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": true,
|
||||
|
|
@ -35,9 +43,58 @@
|
|||
}
|
||||
},
|
||||
"lint": {
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"{projectRoot}/src/**/*.{ts,json}"
|
||||
],
|
||||
"maxWarnings": 0
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"lintFilePatterns": [
|
||||
"{projectRoot}/src/**/*.{ts,json}"
|
||||
],
|
||||
"maxWarnings": 0
|
||||
},
|
||||
"fix": {}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": [
|
||||
"{workspaceRoot}/coverage/{projectRoot}"
|
||||
],
|
||||
"options": {
|
||||
"jestConfig": "{projectRoot}/jest.config.mjs"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"coverage": true,
|
||||
"watchAll": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"test:e2e": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/twenty-sdk",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,22 @@ const NX_PROJECT_CONFIGURATION_PATH = path.join(
|
|||
PACKAGE_PATH,
|
||||
NX_PROJECT_CONFIGURATION_FILENAME,
|
||||
);
|
||||
const EXCLUDED_EXTENSIONS = [
|
||||
'**/*.test.ts',
|
||||
'**/*.test.tsx',
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.tsx',
|
||||
'**/*.stories.ts',
|
||||
'**/*.stories.tsx',
|
||||
] as const;
|
||||
const EXCLUDED_DIRECTORIES = [
|
||||
'**/__tests__/**',
|
||||
'**/__mocks__/**',
|
||||
'**/__stories__/**',
|
||||
'**/internal/**',
|
||||
'**/cli/**',
|
||||
] as const;
|
||||
const ROOT_DIRECTORIES = ['application'];
|
||||
|
||||
const prettierConfigFile = prettier.resolveConfigFile();
|
||||
if (prettierConfigFile == null) {
|
||||
|
|
@ -257,24 +273,10 @@ const computeProjectNxBuildOutputsPath = (moduleDirectories: string[]) => {
|
|||
return ['{projectRoot}/dist', ...dynamicOutputsPath];
|
||||
};
|
||||
|
||||
const EXCLUDED_EXTENSIONS = [
|
||||
'**/*.test.ts',
|
||||
'**/*.test.tsx',
|
||||
'**/*.spec.ts',
|
||||
'**/*.spec.tsx',
|
||||
'**/*.stories.ts',
|
||||
'**/*.stories.tsx',
|
||||
] as const;
|
||||
const EXCLUDED_DIRECTORIES = [
|
||||
'**/__tests__/**',
|
||||
'**/__mocks__/**',
|
||||
'**/__stories__/**',
|
||||
'**/internal/**',
|
||||
] as const;
|
||||
function getTypeScriptFiles(
|
||||
const getTypeScriptFiles = (
|
||||
directoryPath: string,
|
||||
includeIndex: boolean = false,
|
||||
): string[] {
|
||||
): string[] => {
|
||||
const pattern = slash(path.join(directoryPath, '**', '*.{ts,tsx}'));
|
||||
const files = globSync(pattern, {
|
||||
cwd: SRC_PATH,
|
||||
|
|
@ -287,7 +289,7 @@ function getTypeScriptFiles(
|
|||
!file.endsWith('.d.ts') &&
|
||||
(includeIndex ? true : !file.endsWith('index.ts')),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getKind = (
|
||||
node: ts.VariableStatement,
|
||||
|
|
@ -305,10 +307,10 @@ const getKind = (
|
|||
return 'var';
|
||||
};
|
||||
|
||||
function extractExportsFromSourceFile(sourceFile: ts.SourceFile) {
|
||||
const extractExportsFromSourceFile = (sourceFile: ts.SourceFile) => {
|
||||
const exports: DeclarationOccurrence[] = [];
|
||||
|
||||
function visit(node: ts.Node) {
|
||||
const visit = (node: ts.Node) => {
|
||||
if (!ts.canHaveModifiers(node)) {
|
||||
return ts.forEachChild(node, visit);
|
||||
}
|
||||
|
|
@ -409,11 +411,11 @@ function extractExportsFromSourceFile(sourceFile: ts.SourceFile) {
|
|||
break;
|
||||
}
|
||||
return ts.forEachChild(node, visit);
|
||||
}
|
||||
};
|
||||
|
||||
visit(sourceFile);
|
||||
return exports;
|
||||
}
|
||||
};
|
||||
|
||||
type ExportKind =
|
||||
| 'type'
|
||||
|
|
@ -430,7 +432,7 @@ type FileExports = Array<{
|
|||
exports: DeclarationOccurrence[];
|
||||
}>;
|
||||
|
||||
function findAllExports(directoryPath: string): FileExports {
|
||||
const findAllExports = (directoryPath: string): FileExports => {
|
||||
const results: FileExports = [];
|
||||
|
||||
const files = getTypeScriptFiles(directoryPath);
|
||||
|
|
@ -453,7 +455,7 @@ function findAllExports(directoryPath: string): FileExports {
|
|||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
};
|
||||
|
||||
type ExportByBarrel = {
|
||||
barrel: {
|
||||
|
|
@ -484,15 +486,39 @@ const retrieveExportsByBarrel = (barrelDirectories: string[]) => {
|
|||
|
||||
const main = () => {
|
||||
const moduleDirectories = getSubDirectoryPaths(SRC_PATH);
|
||||
|
||||
const rootDirectory = moduleDirectories.find((dir) =>
|
||||
ROOT_DIRECTORIES.includes(getLastPathFolder(dir)),
|
||||
);
|
||||
|
||||
const otherBarrelDirectories = moduleDirectories.filter(
|
||||
(dir) => !ROOT_DIRECTORIES.includes(getLastPathFolder(dir)),
|
||||
);
|
||||
|
||||
const exportsByBarrel = retrieveExportsByBarrel(moduleDirectories);
|
||||
const moduleIndexFiles = generateModuleIndexFiles(exportsByBarrel);
|
||||
const packageJsonConfig =
|
||||
computePackageJsonFilesAndExportsConfig(moduleDirectories);
|
||||
const nxBuildOutputsPath =
|
||||
computeProjectNxBuildOutputsPath(moduleDirectories);
|
||||
|
||||
const packageJsonConfig = computePackageJsonFilesAndExportsConfig(
|
||||
otherBarrelDirectories,
|
||||
);
|
||||
const nxBuildOutputsPath = computeProjectNxBuildOutputsPath(
|
||||
otherBarrelDirectories,
|
||||
);
|
||||
|
||||
updateNxProjectConfigurationBuildOutputs(nxBuildOutputsPath);
|
||||
writeInPackageJson(packageJsonConfig);
|
||||
moduleIndexFiles.forEach(createTypeScriptFile);
|
||||
|
||||
// Ensure top-level src/index.ts re-exports the root directories barrel so consumers can `import * from "twenty-sdk"`
|
||||
// We intentionally keep this file minimal: it delegates to the generated src/<rootDirectory>/index.ts
|
||||
if (rootDirectory) {
|
||||
createTypeScriptFile({
|
||||
path: SRC_PATH,
|
||||
filename: INDEX_FILENAME,
|
||||
content: ROOT_DIRECTORIES.map(
|
||||
(rootDirectory) => `export * from "./${rootDirectory}";`,
|
||||
).join('\n'),
|
||||
});
|
||||
}
|
||||
};
|
||||
main();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type SyncableEntityOptions } from '@/application/syncable-entity-options.type';
|
||||
import { type SyncableEntityOptions } from './syncable-entity-options.type';
|
||||
|
||||
type ApplicationVariable = SyncableEntityOptions & {
|
||||
value?: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type SyncableEntityOptions } from '@/application/syncable-entity-options.type';
|
||||
import { type SyncableEntityOptions } from '../syncable-entity-options.type';
|
||||
|
||||
import {
|
||||
type FieldMetadataType,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type SyncableEntityOptions } from '@/application/syncable-entity-options.type';
|
||||
import { type SyncableEntityOptions } from '../syncable-entity-options.type';
|
||||
|
||||
import {
|
||||
type RelationOnDeleteAction,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type SyncableEntityOptions } from '@/application/syncable-entity-options.type';
|
||||
import { type SyncableEntityOptions } from './syncable-entity-options.type';
|
||||
|
||||
type RouteTrigger = {
|
||||
type: 'route';
|
||||
|
|
@ -17,12 +17,12 @@ type DatabaseEventTrigger = {
|
|||
eventName: string;
|
||||
};
|
||||
|
||||
type ServerlessFunctionTrigger = SyncableEntityOptions &
|
||||
type FunctionTrigger = SyncableEntityOptions &
|
||||
(RouteTrigger | CronTrigger | DatabaseEventTrigger);
|
||||
|
||||
export type ServerlessFunctionConfig = SyncableEntityOptions & {
|
||||
export type FunctionConfig = SyncableEntityOptions & {
|
||||
name?: string;
|
||||
description?: string;
|
||||
timeoutSeconds?: number;
|
||||
triggers?: ServerlessFunctionTrigger[];
|
||||
triggers?: FunctionTrigger[];
|
||||
};
|
||||
|
|
@ -23,7 +23,7 @@ export { Field } from './field-metadata/field.decorator';
|
|||
export { OnDeleteAction } from './field-metadata/on-delete-action';
|
||||
export { RelationType } from './field-metadata/relation-type';
|
||||
export { Relation } from './field-metadata/relation.decorator';
|
||||
export type { FunctionConfig } from './function-config';
|
||||
export { Object } from './object-metadata/object.decorator';
|
||||
export { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from './object-metadata/standard-object-ids';
|
||||
export type { ServerlessFunctionConfig } from './serverless-function-config';
|
||||
export type { SyncableEntityOptions } from './syncable-entity-options.type';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type SyncableEntityOptions } from '@/application/syncable-entity-options.type';
|
||||
import { type SyncableEntityOptions } from '../syncable-entity-options.type';
|
||||
|
||||
type ObjectMetadataOptions = SyncableEntityOptions & {
|
||||
nameSingular: string;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TwentyConfig } from '../../../types/config.types';
|
||||
import { type TwentyConfig } from '../../../types/config.types';
|
||||
|
||||
export const testConfig: TwentyConfig = {
|
||||
apiUrl: 'http://localhost:3000',
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { exec } from 'child_process';
|
||||
|
||||
export default async function globalTeardown() {
|
||||
return new Promise<void>((resolve) => {
|
||||
export default async () =>
|
||||
new Promise<void>((resolve) => {
|
||||
exec('pkill -f "nest start" || true', (error: unknown) => {
|
||||
if (error) {
|
||||
console.log('No server processes to kill');
|
||||
|
|
@ -11,4 +11,3 @@ export default async function globalTeardown() {
|
|||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
import path from 'path';
|
||||
|
||||
export const getTestedApplicationPath = (relativePath: string): string => {
|
||||
const currentFileDir = __dirname;
|
||||
|
||||
const twentyAppsPath = path.resolve(
|
||||
currentFileDir,
|
||||
'../../../../../twenty-apps',
|
||||
__dirname,
|
||||
'../../../../../../twenty-apps',
|
||||
);
|
||||
|
||||
return path.join(twentyAppsPath, relativePath);
|
||||
|
|
@ -8,7 +8,7 @@ import { AuthCommand } from './commands/auth.command';
|
|||
import { ConfigService } from './services/config.service';
|
||||
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(join(__dirname, '../package.json'), 'utf-8'),
|
||||
readFileSync(join(__dirname, '../../package.json'), 'utf-8'),
|
||||
);
|
||||
|
||||
const program = new Command();
|
||||
|
|
@ -5,13 +5,13 @@ import { join } from 'path';
|
|||
import camelcase from 'lodash.camelcase';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory';
|
||||
import { getObjectDecoratedClass } from '../utils/get-object-decorated-class';
|
||||
import { getServerlessFunctionBaseFile } from '../utils/get-serverless-function-base-file';
|
||||
import { getFunctionBaseFile } from '../utils/get-function-base-file';
|
||||
import { convertToLabel } from '../utils/convert-to-label';
|
||||
|
||||
export enum SyncableEntity {
|
||||
AGENT = 'agent',
|
||||
OBJECT = 'object',
|
||||
SERVERLESS_FUNCTION = 'serverlessFunction',
|
||||
FUNCTION = 'function',
|
||||
}
|
||||
|
||||
export const isSyncableEntity = (value: string): value is SyncableEntity => {
|
||||
|
|
@ -44,12 +44,12 @@ export class AppAddCommand {
|
|||
return;
|
||||
}
|
||||
|
||||
if (entity === SyncableEntity.SERVERLESS_FUNCTION) {
|
||||
if (entity === SyncableEntity.FUNCTION) {
|
||||
const entityName = await this.getEntityName(entity);
|
||||
|
||||
const objectFileName = `${camelcase(entityName)}.ts`;
|
||||
|
||||
const decoratedServerlessFunction = getServerlessFunctionBaseFile({
|
||||
const decoratedServerlessFunction = getFunctionBaseFile({
|
||||
name: entityName,
|
||||
});
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export class AppAddCommand {
|
|||
name: 'entity',
|
||||
message: `What entity do you want to create?`,
|
||||
default: '',
|
||||
choices: [SyncableEntity.SERVERLESS_FUNCTION, SyncableEntity.OBJECT],
|
||||
choices: [SyncableEntity.FUNCTION, SyncableEntity.OBJECT],
|
||||
},
|
||||
]);
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|||
import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory';
|
||||
import { ApiService } from '../services/api.service';
|
||||
import { GenerateService } from '../services/generate.service';
|
||||
import { ApiResponse } from '../types/config.types';
|
||||
import { type ApiResponse } from '../types/config.types';
|
||||
import { loadManifest } from '../utils/load-manifest';
|
||||
|
||||
export class AppSyncCommand {
|
||||
|
|
@ -49,7 +49,7 @@ export class AppSyncCommand {
|
|||
});
|
||||
}
|
||||
|
||||
if (!serverlessSyncResult.success) {
|
||||
if (serverlessSyncResult.success === false) {
|
||||
console.error(
|
||||
chalk.red('❌ Serverless functions Sync failed:'),
|
||||
serverlessSyncResult.error,
|
||||
|
|
@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|||
import inquirer from 'inquirer';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory';
|
||||
import { ApiService } from '../services/api.service';
|
||||
import { ApiResponse } from '../types/config.types';
|
||||
import { type ApiResponse } from '../types/config.types';
|
||||
import { loadManifest } from '../utils/load-manifest';
|
||||
|
||||
export class AppUninstallCommand {
|
||||
|
|
@ -31,7 +31,7 @@ export class AppUninstallCommand {
|
|||
manifest.application.universalIdentifier,
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
if (result.success === false) {
|
||||
console.error(chalk.red('❌ Uninstall failed:'), result.error);
|
||||
} else {
|
||||
console.log(chalk.green('✅ Application uninstalled successfully'));
|
||||
|
|
@ -7,7 +7,6 @@ import {
|
|||
} from './app-add.command';
|
||||
import { AppUninstallCommand } from './app-uninstall.command';
|
||||
import { AppDevCommand } from './app-dev.command';
|
||||
import { AppInitCommand } from './app-init.command';
|
||||
import { AppSyncCommand } from './app-sync.command';
|
||||
import { formatPath } from '../utils/format-path';
|
||||
import { AppGenerateCommand } from './app-generate.command';
|
||||
|
|
@ -16,7 +15,6 @@ export class AppCommand {
|
|||
private devCommand = new AppDevCommand();
|
||||
private syncCommand = new AppSyncCommand();
|
||||
private uninstallCommand = new AppUninstallCommand();
|
||||
private initCommand = new AppInitCommand();
|
||||
private addCommand = new AppAddCommand();
|
||||
private generateCommand = new AppGenerateCommand();
|
||||
|
||||
|
|
@ -84,21 +82,6 @@ export class AppCommand {
|
|||
}
|
||||
});
|
||||
|
||||
appCommand
|
||||
.command('init [directory]')
|
||||
.description('Initialize a new Twenty application')
|
||||
.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 this.initCommand.execute(directory);
|
||||
});
|
||||
|
||||
appCommand
|
||||
.command('add [entityType]')
|
||||
.option('--path <path>', 'Path in which the entity should be created.')
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { TwentyConfig } from '../types/config.types';
|
||||
import { type TwentyConfig } from '../types/config.types';
|
||||
|
||||
type PersistedConfig = TwentyConfig & {
|
||||
profiles?: Record<string, TwentyConfig>;
|
||||
|
|
@ -69,7 +69,7 @@ export class ConfigService {
|
|||
raw.profiles = {};
|
||||
}
|
||||
|
||||
const currentProfile = raw.profiles[profile] || {};
|
||||
const currentProfile = raw.profiles[profile] || { apiUrl: '' };
|
||||
|
||||
raw.profiles[profile] = { ...currentProfile, ...config };
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { convertToLabel } from '../convert-to-label';
|
||||
|
||||
describe('convertToLabel', () => {
|
||||
it('should convert to label', () => {
|
||||
expect(convertToLabel('toto')).toBe('Toto');
|
||||
expect(convertToLabel('totoTata')).toBe('Toto tata');
|
||||
expect(convertToLabel('totoTataTiti')).toBe('Toto tata titi');
|
||||
expect(convertToLabel('toto-tata-titi')).toBe('Toto tata titi');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import { getServerlessFunctionBaseFile } from '../get-serverless-function-base-file';
|
||||
import { getFunctionBaseFile } from '../get-function-base-file';
|
||||
|
||||
describe('getServerlessFunctionBaseFile', () => {
|
||||
describe('getFunctionBaseFile', () => {
|
||||
it('should render proper file', () => {
|
||||
expect(
|
||||
getServerlessFunctionBaseFile({
|
||||
getFunctionBaseFile({
|
||||
name: 'serverless-function-name',
|
||||
universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4',
|
||||
}),
|
||||
)
|
||||
.toBe(`import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
).toBe(`import { type FunctionConfig } from 'twenty-sdk';
|
||||
|
||||
export const main = async (params: {
|
||||
a: string;
|
||||
|
|
@ -23,7 +22,7 @@ export const main = async (params: {
|
|||
return { message };
|
||||
};
|
||||
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4',
|
||||
name: 'serverless-function-name',
|
||||
timeoutSeconds: 5,
|
||||
|
|
@ -14,7 +14,7 @@ describe('getObjectDecoratedClass', () => {
|
|||
name: 'MyNewObject',
|
||||
}),
|
||||
).toBe(
|
||||
`import { Object } from 'twenty-sdk/application';
|
||||
`import { Object } from 'twenty-sdk';
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '4122a047-260f-4cf1-bf4f-a268579d7ddf',
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { ensureDirSync, removeSync, writeFileSync } from 'fs-extra';
|
||||
import { ensureDirSync, writeFileSync, removeSync } from 'fs-extra';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { copyBaseApplicationProject } from '../app-template';
|
||||
import { loadManifest } from '../load-manifest';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const write = (root: string, file: string, content: string) => {
|
||||
const abs = join(root, file);
|
||||
|
|
@ -22,7 +22,7 @@ const tsLibMock = `declare module 'tslib' {
|
|||
}`;
|
||||
|
||||
const twentySdkTypesMock = `
|
||||
declare module 'twenty-sdk/application' {
|
||||
declare module 'twenty-sdk' {
|
||||
export type SyncableEntityOptions = { universalIdentifier: string };
|
||||
|
||||
type ApplicationVariable = SyncableEntityOptions & {
|
||||
|
|
@ -58,7 +58,7 @@ declare module 'twenty-sdk/application' {
|
|||
type ServerlessFunctionTrigger = SyncableEntityOptions &
|
||||
(RouteTrigger | CronTrigger | DatabaseEventTrigger);
|
||||
|
||||
export type ServerlessFunctionConfig = SyncableEntityOptions & {
|
||||
export type FunctionConfig = SyncableEntityOptions & {
|
||||
name?: string;
|
||||
description?: string;
|
||||
timeoutSeconds?: number;
|
||||
|
|
@ -93,13 +93,13 @@ declare module 'twenty-sdk/application' {
|
|||
`;
|
||||
|
||||
const serverlessFunctionMock = `
|
||||
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
import { type FunctionConfig } from 'twenty-sdk';
|
||||
|
||||
export const main = async (params: any): Promise<any> => {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'hello',
|
||||
timeoutSeconds: 2,
|
||||
|
|
@ -129,7 +129,7 @@ const objectMock = `import {
|
|||
BaseObjectMetadata,
|
||||
FieldMetadata,
|
||||
FieldMetadataType
|
||||
} from 'twenty-sdk/application';
|
||||
} from 'twenty-sdk';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
|
|
@ -215,19 +215,97 @@ export class PostCard extends BaseObjectMetadata {
|
|||
}
|
||||
`;
|
||||
|
||||
const packageJsonMock = {
|
||||
name: 'my-app',
|
||||
version: '0.0.1',
|
||||
license: 'MIT',
|
||||
engines: {
|
||||
node: '^24.5.0',
|
||||
npm: 'please-use-yarn',
|
||||
yarn: '>=4.0.2',
|
||||
},
|
||||
packageManager: 'yarn@4.9.2',
|
||||
scripts: {
|
||||
'create-entity': 'twenty app add',
|
||||
dev: 'twenty app dev',
|
||||
generate: 'twenty app generate',
|
||||
sync: 'twenty app sync',
|
||||
uninstall: 'twenty app uninstall',
|
||||
auth: 'twenty auth login',
|
||||
},
|
||||
dependencies: {
|
||||
'twenty-sdk': '0.1.0',
|
||||
},
|
||||
devDependencies: {
|
||||
'@types/node': '^24.7.2',
|
||||
typescript: '^5.9.3',
|
||||
},
|
||||
};
|
||||
|
||||
const tsConfigJsonMock = {
|
||||
compileOnSave: false,
|
||||
compilerOptions: {
|
||||
sourceMap: true,
|
||||
declaration: true,
|
||||
outDir: './dist',
|
||||
rootDir: '.',
|
||||
moduleResolution: 'node',
|
||||
allowSyntheticDefaultImports: true,
|
||||
emitDecoratorMetadata: true,
|
||||
experimentalDecorators: true,
|
||||
importHelpers: true,
|
||||
allowUnreachableCode: false,
|
||||
strictNullChecks: true,
|
||||
alwaysStrict: true,
|
||||
noImplicitAny: true,
|
||||
strictBindCallApply: false,
|
||||
target: 'es2018',
|
||||
module: 'esnext',
|
||||
lib: ['es2020', 'dom'],
|
||||
skipLibCheck: true,
|
||||
skipDefaultLibCheck: true,
|
||||
resolveJsonModule: true,
|
||||
},
|
||||
|
||||
exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts'],
|
||||
};
|
||||
|
||||
const yarnLockMock = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
`;
|
||||
|
||||
const applicationConfigMock = `import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '${v4()}',
|
||||
displayName: 'My App',
|
||||
description: 'My app description',
|
||||
};
|
||||
|
||||
export default config;
|
||||
`;
|
||||
|
||||
describe('loadManifest (integration)', () => {
|
||||
const appName = 'my-app';
|
||||
const appDisplayName = 'My App';
|
||||
const appDescription = 'My app description';
|
||||
const appDirectory = join(tmpdir(), 'twenty-manifest-');
|
||||
const appDirectory = join(tmpdir(), 'test-app');
|
||||
|
||||
beforeEach(async () => {
|
||||
await copyBaseApplicationProject({
|
||||
appName,
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
await ensureDirSync(appDirectory);
|
||||
|
||||
write(appDirectory, 'yarn.lock', yarnLockMock);
|
||||
|
||||
write(appDirectory, 'application.config.ts', applicationConfigMock);
|
||||
|
||||
write(
|
||||
appDirectory,
|
||||
});
|
||||
'tsconfig.json',
|
||||
JSON.stringify(tsConfigJsonMock, null, 2),
|
||||
);
|
||||
|
||||
write(
|
||||
appDirectory,
|
||||
'package.json',
|
||||
JSON.stringify(packageJsonMock, null, 2),
|
||||
);
|
||||
|
||||
write(appDirectory, 'src/Account.ts', objectMock);
|
||||
|
||||
|
|
@ -258,10 +336,11 @@ describe('loadManifest (integration)', () => {
|
|||
expect(packageJson.name).toBe('my-app');
|
||||
expect(packageJson.version).toBe('0.0.1');
|
||||
expect(packageJson.license).toBe('MIT');
|
||||
expect(yarnLock).toContain('# This file is generated by running ');
|
||||
expect(yarnLock).toContain(
|
||||
'# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.',
|
||||
);
|
||||
|
||||
// application
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { universalIdentifier: _, ...otherInfo } = manifest.application;
|
||||
expect(otherInfo).toEqual({
|
||||
displayName: 'My App',
|
||||
|
|
@ -271,7 +350,6 @@ describe('loadManifest (integration)', () => {
|
|||
expect(manifest.objects.length).toBe(1);
|
||||
|
||||
for (const object of manifest.objects) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { universalIdentifier: _, fields, ...otherInfo } = object;
|
||||
expect(otherInfo).toEqual({
|
||||
description: ' A post card object',
|
||||
|
|
@ -341,9 +419,7 @@ describe('loadManifest (integration)', () => {
|
|||
// serverless functions
|
||||
for (const serverlessFunction of manifest.serverlessFunctions) {
|
||||
const {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
universalIdentifier: _,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
handlerPath: __,
|
||||
triggers,
|
||||
...otherInfo
|
||||
|
|
@ -356,7 +432,6 @@ describe('loadManifest (integration)', () => {
|
|||
});
|
||||
|
||||
for (const trigger of triggers) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { universalIdentifier: _, ...otherInfo } = trigger;
|
||||
switch (trigger.type) {
|
||||
case 'route':
|
||||
6
packages/twenty-sdk/src/cli/utils/convert-to-label.ts
Normal file
6
packages/twenty-sdk/src/cli/utils/convert-to-label.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { startCase } from 'lodash';
|
||||
|
||||
export const convertToLabel = (str: string) => {
|
||||
const s = startCase(str).toLowerCase();
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
};
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
import ts, { formatDiagnosticsWithColorAndContext, sys } from 'typescript';
|
||||
import {
|
||||
type Diagnostic,
|
||||
formatDiagnosticsWithColorAndContext,
|
||||
sys,
|
||||
} from 'typescript';
|
||||
|
||||
export const formatAndWarnTsDiagnostics = ({
|
||||
diagnostics,
|
||||
}: {
|
||||
diagnostics: ts.Diagnostic[];
|
||||
diagnostics: Diagnostic[];
|
||||
}) => {
|
||||
if (diagnostics.length > 0) {
|
||||
const formattedDiagnostics = formatDiagnosticsWithColorAndContext(
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import kebabCase from 'lodash.kebabcase';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const getServerlessFunctionBaseFile = ({
|
||||
export const getFunctionBaseFile = ({
|
||||
name,
|
||||
universalIdentifier = v4(),
|
||||
}: {
|
||||
|
|
@ -10,7 +10,7 @@ export const getServerlessFunctionBaseFile = ({
|
|||
}) => {
|
||||
const kebabCaseName = kebabCase(name);
|
||||
|
||||
return `import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
return `import { type FunctionConfig } from 'twenty-sdk';
|
||||
|
||||
export const main = async (params: {
|
||||
a: string;
|
||||
|
|
@ -25,7 +25,7 @@ export const main = async (params: {
|
|||
return { message };
|
||||
};
|
||||
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: '${kebabCaseName}',
|
||||
timeoutSeconds: 5,
|
||||
|
|
@ -15,7 +15,7 @@ export const getObjectDecoratedClass = ({
|
|||
|
||||
const className = camelCaseName[0].toUpperCase() + camelCaseName.slice(1);
|
||||
|
||||
return `import { Object } from 'twenty-sdk/application';
|
||||
return `import { Object } from 'twenty-sdk';
|
||||
|
||||
@Object({
|
||||
${decoratorOptions}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import ts from 'typescript';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
createProgram,
|
||||
|
|
@ -6,6 +5,8 @@ import {
|
|||
parseJsonConfigFileContent,
|
||||
readConfigFile,
|
||||
sys,
|
||||
type Program,
|
||||
type Diagnostic,
|
||||
} from 'typescript';
|
||||
|
||||
const getProgramFromTsconfig = ({
|
||||
|
|
@ -41,7 +42,7 @@ export const getTsProgramAndDiagnostics = async ({
|
|||
appPath,
|
||||
}: {
|
||||
appPath: string;
|
||||
}): Promise<{ program: ts.Program; diagnostics: ts.Diagnostic[] }> => {
|
||||
}): Promise<{ program: Program; diagnostics: Diagnostic[] }> => {
|
||||
const program = getProgramFromTsconfig({
|
||||
appPath,
|
||||
tsconfigPath: 'tsconfig.json',
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import { ParseError, parse as parseJsonc } from 'jsonc-parser';
|
||||
import { type ParseError, parse as parseJsonc } from 'jsonc-parser';
|
||||
|
||||
export interface JsoncParseOptions {
|
||||
allowTrailingComma?: boolean;
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import * as fs from 'fs-extra';
|
||||
import { posix, relative, sep } from 'path';
|
||||
import {
|
||||
Decorator,
|
||||
Expression,
|
||||
FunctionDeclaration,
|
||||
Modifier,
|
||||
Node,
|
||||
Program,
|
||||
SourceFile,
|
||||
type Decorator,
|
||||
type Expression,
|
||||
type FunctionDeclaration,
|
||||
type Modifier,
|
||||
type Node,
|
||||
type Program,
|
||||
type SourceFile,
|
||||
SyntaxKind,
|
||||
VariableDeclaration,
|
||||
type VariableDeclaration,
|
||||
forEachChild,
|
||||
getDecorators,
|
||||
isArrayLiteralExpression,
|
||||
|
|
@ -34,13 +34,13 @@ import {
|
|||
} from 'typescript';
|
||||
import { GENERATED_FOLDER_NAME } from '../services/generate.service';
|
||||
import {
|
||||
AppManifest,
|
||||
Application,
|
||||
FieldMetadata,
|
||||
ObjectManifest,
|
||||
PackageJson,
|
||||
ServerlessFunctionManifest,
|
||||
Sources,
|
||||
type AppManifest,
|
||||
type Application,
|
||||
type FieldMetadata,
|
||||
type ObjectManifest,
|
||||
type PackageJson,
|
||||
type ServerlessFunctionManifest,
|
||||
type Sources,
|
||||
} from '../types/config.types';
|
||||
import { findPathFile } from '../utils/find-path-file';
|
||||
import { getTsProgramAndDiagnostics } from '../utils/get-ts-program-and-diagnostics';
|
||||
|
|
@ -210,7 +210,7 @@ const hasExportModifier = (st: any) =>
|
|||
/**
|
||||
* Finds (and validates) the new serverless file shape:
|
||||
* - exactly 2 exported bindings
|
||||
* - one must be `config` (typed ServerlessFunctionConfig)
|
||||
* - one must be `config` (typed FunctionConfig)
|
||||
* - the other must be a function (exported function declaration, or const initialized with arrow/function expression)
|
||||
*/
|
||||
const findHandlerAndConfig = (
|
||||
|
|
@ -290,13 +290,13 @@ const findHandlerAndConfig = (
|
|||
`"config" in ${sf.fileName} must be initialized to an object literal.`,
|
||||
);
|
||||
}
|
||||
// (Light) type guard: ensure declared type mentions ServerlessFunctionConfig if present
|
||||
// (Light) type guard: ensure declared type mentions FunctionConfig if present
|
||||
const maybeVarDecl = configExport.declNode as VariableDeclaration;
|
||||
if ('type' in maybeVarDecl && maybeVarDecl.type) {
|
||||
const typeText = maybeVarDecl.type.getText(sf);
|
||||
if (!/\bServerlessFunctionConfig\b/.test(typeText)) {
|
||||
if (!/\bFunctionConfig\b/.test(typeText)) {
|
||||
throw new Error(
|
||||
`"config" in ${sf.fileName} must be typed as ServerlessFunctionConfig (got: ${typeText}).`,
|
||||
`"config" in ${sf.fileName} must be typed as FunctionConfig (got: ${typeText}).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,10 @@
|
|||
export default {};
|
||||
/*
|
||||
* _____ _
|
||||
*|_ _|_ _____ _ __ | |_ _ _
|
||||
* | | \ \ /\ / / _ \ '_ \| __| | | | Auto-generated file
|
||||
* | | \ V V / __/ | | | |_| |_| | Any edits to this will be overridden
|
||||
* |_| \_/\_/ \___|_| |_|\__|\__, |
|
||||
* |___/
|
||||
*/
|
||||
|
||||
export * from './application';
|
||||
|
|
|
|||
26
packages/twenty-sdk/tsconfig.cli.json
Normal file
26
packages/twenty-sdk/tsconfig.cli.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/cli",
|
||||
"rootDir": "./src/cli",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"isolatedModules": false
|
||||
},
|
||||
"include": ["src/cli/**/*.ts"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.e2e-spec.ts",
|
||||
"**/__tests__/**"
|
||||
]
|
||||
}
|
||||
|
|
@ -9,12 +9,9 @@
|
|||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"sourceMap": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
|
|
@ -1,28 +1,36 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "src",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"allowUnreachableCode": false,
|
||||
"strictNullChecks": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"lib": ["es2020", "dom"],
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.cli.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.e2e.json"
|
||||
}
|
||||
],
|
||||
"include": ["src/**/*.ts", "vite.config.ts"],
|
||||
"exclude": [
|
||||
"src/cli/**",
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.e2e-spec.ts",
|
||||
"**/__tests__/**"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,11 @@ export default defineConfig(() => {
|
|||
tsconfigPaths({
|
||||
root: __dirname,
|
||||
}),
|
||||
dts({ entryRoot: './src', tsconfigPath: tsConfigPath }),
|
||||
dts({
|
||||
entryRoot: './src',
|
||||
tsconfigPath: tsConfigPath,
|
||||
exclude: ['vite.config.ts'],
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
|
|
|
|||
87
yarn.lock
87
yarn.lock
|
|
@ -26753,7 +26753,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.12.0, ajv@npm:^8.17.1, ajv@npm:^8.9.0":
|
||||
"ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.17.1, ajv@npm:^8.9.0":
|
||||
version: 8.17.1
|
||||
resolution: "ajv@npm:8.17.1"
|
||||
dependencies:
|
||||
|
|
@ -30919,6 +30919,30 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"create-twenty-app@workspace:packages/create-twenty-app":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "create-twenty-app@workspace:packages/create-twenty-app"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@types/fs-extra": "npm:^11.0.0"
|
||||
"@types/inquirer": "npm:^9.0.0"
|
||||
"@types/lodash.camelcase": "npm:^4.3.7"
|
||||
"@types/lodash.kebabcase": "npm:^4.1.7"
|
||||
"@types/lodash.startcase": "npm:^4"
|
||||
"@types/node": "npm:^20.0.0"
|
||||
chalk: "npm:^5.3.0"
|
||||
commander: "npm:^12.0.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
inquirer: "npm:^10.0.0"
|
||||
lodash.camelcase: "npm:^4.3.0"
|
||||
lodash.kebabcase: "npm:^4.1.1"
|
||||
lodash.startcase: "npm:^4.4.0"
|
||||
uuid: "npm:^13.0.0"
|
||||
bin:
|
||||
create-twenty-app: dist/cli.js
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"crelt@npm:^1.0.0, crelt@npm:^1.0.5":
|
||||
version: 1.0.6
|
||||
resolution: "crelt@npm:1.0.6"
|
||||
|
|
@ -55614,38 +55638,8 @@ __metadata:
|
|||
"twenty-cli@workspace:packages/twenty-cli":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-cli@workspace:packages/twenty-cli"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@types/fs-extra": "npm:^11.0.0"
|
||||
"@types/inquirer": "npm:^9.0.0"
|
||||
"@types/jest": "npm:^29.5.0"
|
||||
"@types/lodash.camelcase": "npm:^4.3.7"
|
||||
"@types/lodash.capitalize": "npm:^4"
|
||||
"@types/lodash.kebabcase": "npm:^4.1.7"
|
||||
"@types/lodash.startcase": "npm:^4"
|
||||
"@types/node": "npm:^20.0.0"
|
||||
ajv: "npm:^8.12.0"
|
||||
ajv-formats: "npm:^2.1.1"
|
||||
axios: "npm:^1.6.0"
|
||||
chalk: "npm:^5.3.0"
|
||||
chokidar: "npm:^4.0.0"
|
||||
commander: "npm:^12.0.0"
|
||||
dotenv: "npm:^16.4.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
inquirer: "npm:^10.0.0"
|
||||
jest: "npm:^29.5.0"
|
||||
jsonc-parser: "npm:^3.2.0"
|
||||
lodash.camelcase: "npm:^4.3.0"
|
||||
lodash.capitalize: "npm:^4.2.1"
|
||||
lodash.kebabcase: "npm:^4.1.1"
|
||||
lodash.startcase: "npm:^4.4.0"
|
||||
tsx: "npm:^4.7.0"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
wait-on: "npm:^7.2.0"
|
||||
bin:
|
||||
twenty: dist/cli.js
|
||||
twenty: ./deprecate.js
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
|
@ -55814,11 +55808,38 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "twenty-sdk@workspace:packages/twenty-sdk"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@types/fs-extra": "npm:^11.0.0"
|
||||
"@types/inquirer": "npm:^9.0.0"
|
||||
"@types/jest": "npm:^29.5.0"
|
||||
"@types/lodash.camelcase": "npm:^4.3.7"
|
||||
"@types/lodash.capitalize": "npm:^4"
|
||||
"@types/lodash.kebabcase": "npm:^4.1.7"
|
||||
"@types/lodash.startcase": "npm:^4"
|
||||
"@types/node": "npm:^24.0.0"
|
||||
typescript: "npm:5.9.2"
|
||||
axios: "npm:^1.6.0"
|
||||
chalk: "npm:^5.3.0"
|
||||
chokidar: "npm:^4.0.0"
|
||||
commander: "npm:^12.0.0"
|
||||
dotenv: "npm:^16.4.0"
|
||||
fs-extra: "npm:^11.2.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
inquirer: "npm:^10.0.0"
|
||||
jest: "npm:^29.5.0"
|
||||
jsonc-parser: "npm:^3.2.0"
|
||||
lodash.camelcase: "npm:^4.3.0"
|
||||
lodash.capitalize: "npm:^4.2.1"
|
||||
lodash.kebabcase: "npm:^4.1.1"
|
||||
lodash.startcase: "npm:^4.4.0"
|
||||
tsx: "npm:^4.7.0"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
vite-plugin-dts: "npm:3.8.1"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
wait-on: "npm:^7.2.0"
|
||||
bin:
|
||||
twenty: dist/cli/cli.js
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue