Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d43ed4515 | ||
|
|
3d1d3d9f04 | ||
|
|
b19dc703cc | ||
|
|
9b668619c5 | ||
|
|
e7c83dc04f | ||
|
|
693fe01fa4 | ||
|
|
c616713435 | ||
|
|
859c948d01 |
@@ -12,7 +12,6 @@ This directory contains Twenty's development guidelines and best practices in th
|
||||
### Core Guidelines
|
||||
- **architecture.mdc** - Project overview, technology stack, and infrastructure setup (Always Applied)
|
||||
- **nx-rules.mdc** - Nx workspace guidelines and best practices (Auto-attached to Nx files)
|
||||
- **server-migrations.mdc** - Backend migration and TypeORM guidelines for `twenty-server` (Auto-attached to server entities and migration files)
|
||||
|
||||
### Code Quality
|
||||
- **typescript-guidelines.mdc** - TypeScript best practices and conventions (Auto-attached to .ts/.tsx files)
|
||||
@@ -41,7 +40,7 @@ You can manually reference any rule using the `@ruleName` syntax:
|
||||
- `@testing-guidelines` - Get testing recommendations
|
||||
|
||||
### Rule Types Used
|
||||
- **Always Applied** - Loaded in every context (architecture.mdc, README.mdc)
|
||||
- **Always Applied** - Loaded in every context (architecture.mdc, README.mdc)
|
||||
- **Auto Attached** - Loaded when matching file patterns are referenced
|
||||
- **Agent Requested** - Available for AI to include when relevant
|
||||
- **Manual** - Only included when explicitly mentioned
|
||||
|
||||
@@ -89,8 +89,6 @@ Use the AI codebase search to find:
|
||||
|
||||
### 1. Setup Git Branch
|
||||
|
||||
**IMPORTANT**: Always start from an up-to-date main branch to avoid merge conflicts and ensure the changelog is based on the latest code.
|
||||
|
||||
```bash
|
||||
cd /Users/thomascolasdesfrancs/code/twenty
|
||||
git checkout main
|
||||
@@ -100,8 +98,6 @@ git checkout -b {VERSION}
|
||||
|
||||
Replace `{VERSION}` with the actual version number (e.g., `1.9.0`)
|
||||
|
||||
⚠️ **Do this first** before making any file changes. This ensures your branch is based on the latest main.
|
||||
|
||||
### 2. Create File Structure
|
||||
|
||||
**Create changelog file:**
|
||||
@@ -178,7 +174,6 @@ Description of the third feature.
|
||||
- Focus on user benefits, not technical implementation
|
||||
- Use active voice
|
||||
- Start with what the user can now do
|
||||
- **NEVER mention the brand name "Twenty"** in changelog text - use "your workspace", "the platform", or similar neutral references instead
|
||||
|
||||
**Reference Previous Changelogs:**
|
||||
- Check `packages/twenty-website/src/content/releases/` for examples
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
description: Guidelines for generating and managing TypeORM migrations in twenty-server
|
||||
globs: [
|
||||
"packages/twenty-server/src/**/*.entity.ts",
|
||||
"packages/twenty-server/src/database/typeorm/**/*.ts"
|
||||
]
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
## Server Migrations (twenty-server)
|
||||
|
||||
- **When changing an entity, always generate a migration**
|
||||
- If you modify a `*.entity.ts` file in `packages/twenty-server/src`, you **must** generate a corresponding TypeORM migration instead of manually editing the database schema.
|
||||
- Use the Nx + TypeORM command from the project root:
|
||||
|
||||
```bash
|
||||
npx nx run twenty-server:typeorm migration:generate src/database/typeorm/core/migrations/common/[name] -d src/database/typeorm/core/core.datasource.ts
|
||||
```
|
||||
|
||||
- Replace `[name]` with a descriptive, kebab-case migration name that reflects the change (for example, `add-agent-turn-evaluation`).
|
||||
|
||||
- **Prefer generated migrations over manual edits**
|
||||
- Let TypeORM infer schema changes from the updated entities; only adjust the generated migration file manually if absolutely necessary (for example, for data backfills or complex constraints).
|
||||
- Keep schema changes (DDL) in these generated migrations and avoid mixing in heavy data migrations unless there is a strong reason and clear comments.
|
||||
|
||||
- **Keep migrations consistent and reversible**
|
||||
- Ensure the generated migration includes both `up` and `down` logic that correctly applies and reverts the entity change when possible.
|
||||
- Do not delete or rewrite existing, committed migrations unless you are explicitly working on a pre-release branch where history rewrites are allowed by team conventions.
|
||||
|
||||
@@ -63,4 +63,4 @@ git push origin your-branch-name
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If you face any issues or have suggestions, please feel free to [create an issue on Twenty's GitHub repository](https://github.com/twentyhq/twenty/issues/new). Please provide as much detail as possible.
|
||||
If you face any issues or have suggestions, please feel free to (create an issue on Twenty's GitHub repository)[https://github.com/twentyhq/twenty/issues/new]. Please provide as much detail as possible.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: CI SDK
|
||||
name: CI CLI
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -19,8 +19,9 @@ jobs:
|
||||
uses: ./.github/workflows/changed-files.yaml
|
||||
with:
|
||||
files: |
|
||||
packages/twenty-sdk/**
|
||||
sdk-test:
|
||||
packages/twenty-cli/**
|
||||
packages/twenty-server/**
|
||||
cli-test:
|
||||
needs: changed-files-check
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
timeout-minutes: 30
|
||||
@@ -42,12 +43,12 @@ jobs:
|
||||
- name: Run ${{ matrix.task }} task
|
||||
uses: ./.github/workflows/actions/nx-affected
|
||||
with:
|
||||
tag: scope:sdk
|
||||
tag: scope:cli
|
||||
tasks: ${{ matrix.task }}
|
||||
sdk-e2e-test:
|
||||
cli-e2e-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: depot-ubuntu-24.04-8
|
||||
needs: [changed-files-check, sdk-test]
|
||||
needs: [changed-files-check, cli-test]
|
||||
if: needs.changed-files-check.outputs.any_changed == 'true'
|
||||
services:
|
||||
postgres:
|
||||
@@ -89,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: SDK / Run E2E Tests
|
||||
run: npx nx test:e2e twenty-sdk
|
||||
ci-sdk-status-check:
|
||||
- name: CLI / Run E2E Tests
|
||||
run: npx nx test:e2e twenty-cli
|
||||
ci-cli-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, sdk-test, sdk-e2e-test]
|
||||
needs: [changed-files-check, cli-test, cli-e2e-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
@@ -1,55 +0,0 @@
|
||||
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/**
|
||||
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,8 +1,4 @@
|
||||
name: 'Test Docker Compose'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
|
||||
@@ -124,9 +124,7 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
git commit -m "chore: sync docs artifacts"
|
||||
git push origin "HEAD:$HEAD_REF"
|
||||
env:
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
|
||||
- name: Check for changes and commit
|
||||
if: github.event_name != 'pull_request'
|
||||
|
||||
+7
-2
@@ -9,6 +9,8 @@
|
||||
"@linaria/core": "^6.2.0",
|
||||
"@linaria/react": "^6.2.1",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@sentry/profiling-node": "^9.26.0",
|
||||
"@sentry/react": "^9.26.0",
|
||||
"@sniptt/guards": "^0.2.0",
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@wyw-in-js/vite": "^0.7.0",
|
||||
@@ -187,7 +189,11 @@
|
||||
"ts-node": "10.9.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.17.0",
|
||||
"vite": "^7.0.0"
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-checker": "^0.10.2",
|
||||
"vite-plugin-cjs-interop": "^2.2.0",
|
||||
"vite-plugin-dts": "3.8.1",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
@@ -227,7 +233,6 @@
|
||||
"packages/twenty-sdk",
|
||||
"packages/twenty-apps",
|
||||
"packages/twenty-cli",
|
||||
"packages/create-twenty-app",
|
||||
"tools/eslint-rules"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<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 24+ (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
|
||||
|
||||
# Watch your application's functions logs
|
||||
yarn logs
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
yarn uninstall
|
||||
|
||||
# Display commands' help
|
||||
yarn help
|
||||
```
|
||||
|
||||
## 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)
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.1.3",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "npx rimraf dist && npx vite build"
|
||||
},
|
||||
"keywords": [
|
||||
"twenty",
|
||||
"cli",
|
||||
"crm",
|
||||
"application",
|
||||
"development"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/cli.d.ts",
|
||||
"import": "./dist/cli.mjs",
|
||||
"require": "./dist/cli.cjs"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"vite": "^7.0.0",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"yarn": "^4.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{
|
||||
"name": "create-twenty-app",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "library",
|
||||
"tags": ["scope:create-app"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["{projectRoot}/dist"]
|
||||
},
|
||||
"dev": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/create-twenty-app",
|
||||
"command": "tsx src/cli.ts"
|
||||
}
|
||||
},
|
||||
"start": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/create-twenty-app",
|
||||
"command": "node dist/cli.cjs"
|
||||
}
|
||||
},
|
||||
"typecheck": {},
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import chalk from 'chalk';
|
||||
import { Command, CommanderError } from 'commander';
|
||||
import { CreateAppCommand } from '@/create-app.command';
|
||||
import packageJson from '../package.json';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
24.5.0
|
||||
Binary file not shown.
@@ -1,27 +0,0 @@
|
||||
This is a [Twenty](https://twenty.com) application project bootstrapped with [`create-twenty-app`](https://www.npmjs.com/package/create-twenty-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, authenticate to your workspace:
|
||||
|
||||
```bash
|
||||
yarn auth
|
||||
```
|
||||
|
||||
Then, install this app to your workspace:
|
||||
|
||||
```bash
|
||||
yarn sync
|
||||
```
|
||||
|
||||
Open your Twenty instance and go to `/settings/applications` section to see the result.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Twenty applications, take a look at the following resources:
|
||||
|
||||
- [twenty-sdk](https://www.npmjs.com/package/twenty-sdk) - learn about `twenty-sdk` tool.
|
||||
- [Twenty doc](https://docs.twenty.com/) - Twenty's documentation.
|
||||
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
|
||||
|
||||
You can check out [the Twenty GitHub repository](https://github.com/twentyhq/twenty) - your feedback and contributions are welcome!
|
||||
@@ -1,137 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import typescriptEslint from '@typescript-eslint/eslint-plugin';
|
||||
import typescriptParser from '@typescript-eslint/parser';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
|
||||
import prettierPlugin from 'eslint-plugin-prettier';
|
||||
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
|
||||
|
||||
export default [
|
||||
// Base JS rules
|
||||
js.configs.recommended,
|
||||
|
||||
// Global ignores
|
||||
{
|
||||
ignores: ['**/node_modules/**', '**/dist/**', '**/coverage/**'],
|
||||
},
|
||||
|
||||
// Base config for TS/JS files
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: {
|
||||
prettier: prettierPlugin,
|
||||
import: importPlugin,
|
||||
'prefer-arrow': preferArrowPlugin,
|
||||
'unused-imports': unusedImportsPlugin,
|
||||
},
|
||||
rules: {
|
||||
// General rules (aligned with main project)
|
||||
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
|
||||
'no-console': [
|
||||
'warn',
|
||||
{ allow: ['group', 'groupCollapsed', 'groupEnd'] },
|
||||
],
|
||||
'no-control-regex': 0,
|
||||
'no-debugger': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
'no-undef': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
|
||||
// Import rules
|
||||
'import/no-relative-packages': 'error',
|
||||
'import/no-useless-path-segments': 'error',
|
||||
'import/no-duplicates': ['error', { considerQueryString: true }],
|
||||
|
||||
// Prefer arrow functions
|
||||
'prefer-arrow/prefer-arrow-functions': [
|
||||
'error',
|
||||
{
|
||||
disallowPrototype: true,
|
||||
singleReturnOnly: false,
|
||||
classPropertiesAllowed: false,
|
||||
},
|
||||
],
|
||||
|
||||
// Unused imports
|
||||
'unused-imports/no-unused-imports': 'warn',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
|
||||
// Prettier (formatting as lint errors if you want)
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
// TypeScript-specific configuration
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': typescriptEslint,
|
||||
},
|
||||
rules: {
|
||||
// Turn off base rule and use TS-aware versions
|
||||
'no-redeclare': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{
|
||||
prefer: 'type-imports',
|
||||
fixStyle: 'inline-type-imports',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/no-empty-interface': [
|
||||
'error',
|
||||
{
|
||||
allowSingleExtends: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
|
||||
// Test files (Jest)
|
||||
{
|
||||
files: ['**/*.spec.@(ts|tsx|js|jsx)', '**/*.test.@(ts|tsx|js|jsx)'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
jest: true,
|
||||
describe: true,
|
||||
it: true,
|
||||
expect: true,
|
||||
beforeEach: true,
|
||||
afterEach: true,
|
||||
beforeAll: true,
|
||||
afterAll: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,10 +0,0 @@
|
||||
import { convertToLabel } from '@/utils/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,143 +0,0 @@
|
||||
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 fs.copy(join(__dirname, './constants/base-application'), appDirectory);
|
||||
|
||||
await createPackageJson({ appName, appDirectory });
|
||||
|
||||
await createGitignore(appDirectory);
|
||||
|
||||
await createYarnLock(appDirectory);
|
||||
|
||||
await createApplicationConfig({
|
||||
displayName: appDisplayName,
|
||||
description: 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 createGitignore = async (appDirectory: string) => {
|
||||
const gitignoreContent = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn
|
||||
|
||||
# codegen
|
||||
generated
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# dev
|
||||
/dist/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
`;
|
||||
|
||||
await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent);
|
||||
};
|
||||
|
||||
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.1.0',
|
||||
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',
|
||||
logs: 'twenty app logs',
|
||||
uninstall: 'twenty app uninstall',
|
||||
help: 'twenty help',
|
||||
auth: 'twenty auth login',
|
||||
},
|
||||
dependencies: {
|
||||
'twenty-sdk': '0.1.3',
|
||||
},
|
||||
devDependencies: {
|
||||
'@types/node': '^24.7.2',
|
||||
typescript: '^5.9.3',
|
||||
},
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
join(appDirectory, 'package.json'),
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
'utf8',
|
||||
);
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import { promisify } from 'util';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
export const install = async (root: string) => {
|
||||
try {
|
||||
await execPromise('yarn', { cwd: root });
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('yarn install failed:'), error.stdout);
|
||||
}
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
const isInGitRepository = async (root: string): Promise<boolean> => {
|
||||
try {
|
||||
await execPromise('git rev-parse --is-inside-work-tree', { cwd: root });
|
||||
return true;
|
||||
} catch {
|
||||
// Empty catch block
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isInMercurialRepository = async (root: string): Promise<boolean> => {
|
||||
try {
|
||||
await execPromise('hg --cwd . root', { cwd: root });
|
||||
return true;
|
||||
} catch {
|
||||
// Empty catch block
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isDefaultBranchSet = async (root: string): Promise<boolean> => {
|
||||
try {
|
||||
await execPromise('git config init.defaultBranch', { cwd: root });
|
||||
return true;
|
||||
} catch {
|
||||
// Empty catch block
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const tryGitInit = async (root: string): Promise<boolean> => {
|
||||
try {
|
||||
await execPromise('git --version', { cwd: root });
|
||||
|
||||
if (
|
||||
(await isInGitRepository(root)) ||
|
||||
(await isInMercurialRepository(root))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
await execPromise('git init', { cwd: root });
|
||||
|
||||
try {
|
||||
if (!(await isDefaultBranchSet(root))) {
|
||||
await execPromise('git checkout -b main', { cwd: root });
|
||||
}
|
||||
|
||||
await execPromise('git add -A', { cwd: root });
|
||||
await execPromise(
|
||||
'git commit -m "Initial commit from Create Twenty App"',
|
||||
{
|
||||
cwd: root,
|
||||
},
|
||||
);
|
||||
return true;
|
||||
} catch {
|
||||
fs.rm(join(root, '.git'), { recursive: true, force: true });
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strictNullChecks": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"**/__mocks__/**/*",
|
||||
"vite.config.ts",
|
||||
"jest.config.mjs",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.test.tsx"
|
||||
]
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import packageJson from './package.json';
|
||||
|
||||
const moduleEntries = Object.keys((packageJson as any).exports || {})
|
||||
.filter(
|
||||
(key) => key !== './style.css' && key !== '.' && !key.startsWith('./src/'),
|
||||
)
|
||||
.map((module) => `src/${module.replace(/^\.\//, '')}/index.ts`);
|
||||
|
||||
const entries = ['src/cli.ts', ...moduleEntries];
|
||||
|
||||
const entryFileNames = (chunk: any, extension: 'cjs' | 'mjs') => {
|
||||
if (!chunk.isEntry) {
|
||||
throw new Error(
|
||||
`Should never occurs, encountered a non entry chunk ${chunk.facadeModuleId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const splitFaceModuleId = chunk.facadeModuleId?.split('/');
|
||||
if (splitFaceModuleId === undefined) {
|
||||
throw new Error(
|
||||
`Should never occurs splitFaceModuleId is undefined ${chunk.facadeModuleId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const moduleDirectory = splitFaceModuleId[splitFaceModuleId?.length - 2];
|
||||
if (moduleDirectory === 'src') {
|
||||
return `${chunk.name}.${extension}`;
|
||||
}
|
||||
return `${moduleDirectory}.${extension}`;
|
||||
};
|
||||
|
||||
const copyAssetPlugin = (targets: { src: string; dest: string }[]) => {
|
||||
return {
|
||||
name: 'copy-assets',
|
||||
closeBundle: async () => {
|
||||
for (const target of targets) {
|
||||
await fs.copy(
|
||||
path.resolve(__dirname, target.src),
|
||||
path.resolve(__dirname, target.dest),
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default defineConfig(() => {
|
||||
const tsConfigPath = path.resolve(__dirname, './tsconfig.lib.json');
|
||||
|
||||
return {
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/packages/create-twenty-app',
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
root: __dirname,
|
||||
}),
|
||||
dts({ entryRoot: './src', tsconfigPath: tsConfigPath }),
|
||||
copyAssetPlugin([
|
||||
{
|
||||
src: 'src/constants/base-application',
|
||||
dest: 'dist/constants/base-application',
|
||||
},
|
||||
]),
|
||||
],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
lib: { entry: entries, name: 'create-twenty-app' },
|
||||
rollupOptions: {
|
||||
external: [
|
||||
...Object.keys((packageJson as any).dependencies || {}),
|
||||
'path',
|
||||
'fs',
|
||||
'child_process',
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: 'es',
|
||||
entryFileNames: (chunk) => entryFileNames(chunk, 'mjs'),
|
||||
},
|
||||
{
|
||||
format: 'cjs',
|
||||
interop: 'auto',
|
||||
esModule: true,
|
||||
exports: 'named',
|
||||
entryFileNames: (chunk) => entryFileNames(chunk, 'cjs'),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
logLevel: 'warn',
|
||||
};
|
||||
});
|
||||
@@ -2056,8 +2056,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^10.2.2":
|
||||
version: 10.5.0
|
||||
resolution: "glob@npm:10.5.0"
|
||||
version: 10.4.5
|
||||
resolution: "glob@npm:10.4.5"
|
||||
dependencies:
|
||||
foreground-child: "npm:^3.1.0"
|
||||
jackspeak: "npm:^3.1.2"
|
||||
@@ -2067,7 +2067,7 @@ __metadata:
|
||||
path-scurry: "npm:^1.11.1"
|
||||
bin:
|
||||
glob: dist/esm/bin.mjs
|
||||
checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828
|
||||
checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -239,15 +239,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^24.7.2":
|
||||
version: 24.10.1
|
||||
resolution: "@types/node@npm:24.10.1"
|
||||
dependencies:
|
||||
undici-types: "npm:~7.16.0"
|
||||
checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abbrev@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "abbrev@npm:3.0.1"
|
||||
@@ -581,8 +572,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^10.2.2":
|
||||
version: 10.5.0
|
||||
resolution: "glob@npm:10.5.0"
|
||||
version: 10.4.5
|
||||
resolution: "glob@npm:10.4.5"
|
||||
dependencies:
|
||||
foreground-child: "npm:^3.1.0"
|
||||
jackspeak: "npm:^3.1.2"
|
||||
@@ -592,7 +583,7 @@ __metadata:
|
||||
path-scurry: "npm:^1.11.1"
|
||||
bin:
|
||||
glob: dist/esm/bin.mjs
|
||||
checksum: 10c0/100705eddbde6323e7b35e1d1ac28bcb58322095bd8e63a7d0bef1a2cdafe0d0f7922a981b2b48369a4f8c1b077be5c171804534c3509dfe950dde15fbe6d828
|
||||
checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -916,10 +907,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "rollup-engine@workspace:."
|
||||
dependencies:
|
||||
"@types/node": "npm:^24.7.2"
|
||||
dotenv: "npm:^16.4.5"
|
||||
tsx: "npm:^4.20.6"
|
||||
twenty-sdk: "npm:0.0.3"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -1078,20 +1067,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@npm:0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "twenty-sdk@npm:0.0.3"
|
||||
checksum: 10c0/0a3c85c27edb22fb50f7eb0da4f9770e85729fce05e9e0118ad0cdfc36e42425c93340a6cd1c276daf30aeeaa612db0cd905831c0a8287a31bff3da5be9b0562
|
||||
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
|
||||
|
||||
"unique-filename@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "unique-filename@npm:4.0.0"
|
||||
|
||||
@@ -50,7 +50,7 @@ const returnWorkspaceMemberObjectId = async (): Promise<string> => {
|
||||
).id;
|
||||
};
|
||||
|
||||
const createUpdatedByFieldMetadata = async (
|
||||
const createUpdatedByField = async (
|
||||
sourceObjectId: string,
|
||||
workspaceMemberObjectId: string,
|
||||
objectName: string,
|
||||
@@ -124,7 +124,7 @@ const createUpdatedByFieldMetadata = async (
|
||||
}
|
||||
};
|
||||
|
||||
const updateUpdatedByFieldValue = async (
|
||||
const updateUpdatedByField = async (
|
||||
objectName: string,
|
||||
workspaceMemberId: string,
|
||||
recordId: string,
|
||||
@@ -184,7 +184,7 @@ export const main = async (params: {
|
||||
const workspaceMemberObjectId: string =
|
||||
await returnWorkspaceMemberObjectId();
|
||||
|
||||
const isFieldCreated: boolean | undefined = await createUpdatedByFieldMetadata(
|
||||
const isFieldCreated: boolean | undefined = await createUpdatedByField(
|
||||
objectMetadata.id,
|
||||
workspaceMemberObjectId,
|
||||
objectMetadata.namePlural,
|
||||
@@ -198,7 +198,7 @@ export const main = async (params: {
|
||||
}
|
||||
}
|
||||
|
||||
const isObjectUpdated: boolean | undefined = await updateUpdatedByFieldValue(
|
||||
const isObjectUpdated: boolean | undefined = await updateUpdatedByField(
|
||||
objectMetadata.namePlural,
|
||||
workspaceMemberId,
|
||||
recordId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
import { type ApplicationConfig } from 'twenty-sdk/application';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hello-world",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.2",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
@@ -8,16 +8,8 @@
|
||||
"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.2"
|
||||
"twenty-sdk": "0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FunctionConfig } from 'twenty-sdk';
|
||||
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
import { createClient } from '../../generated';
|
||||
|
||||
export const main = async (params: { recipient?: string }) => {
|
||||
@@ -10,7 +10,6 @@ export const main = async (params: { recipient?: string }) => {
|
||||
Authorization: `Bearer ${process.env.TWENTY_API_KEY}`,
|
||||
},
|
||||
});
|
||||
|
||||
const createPostCard = await client.mutation({
|
||||
createPostCard: {
|
||||
__args: {
|
||||
@@ -22,9 +21,6 @@ export const main = async (params: { recipient?: string }) => {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('createPostCard result', createPostCard);
|
||||
|
||||
return createPostCard;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -32,7 +28,7 @@ export const main = async (params: { recipient?: string }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'create-new-post-card',
|
||||
timeoutSeconds: 2,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Relation,
|
||||
RelationType,
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
|
||||
} from 'twenty-sdk';
|
||||
} from 'twenty-sdk/application';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,13 @@
|
||||
# Deprecated: twenty-cli
|
||||
# Why Twenty CLI?
|
||||
|
||||
This package is deprecated. Please install and use twenty-sdk instead:
|
||||
A command-line interface to easily scaffold, develop, and publish applications that extend Twenty CRM
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm uninstall twenty-cli
|
||||
npm install -g twenty-sdk
|
||||
npm install -g twenty-cli
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/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;
|
||||
@@ -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-sdk-e2e',
|
||||
displayName: 'twenty-cli-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/cli/__tests__/e2e/teardown.ts',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/cli/__tests__/e2e/setupTest.ts'],
|
||||
globalTeardown: '<rootDir>/src/__tests__/e2e/teardown.ts',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/__tests__/e2e/setupTest.ts'],
|
||||
testTimeout: 30000, // 30 seconds timeout for e2e tests
|
||||
maxWorkers: 1,
|
||||
transform: {
|
||||
@@ -1,10 +1,64 @@
|
||||
{
|
||||
"name": "twenty-cli",
|
||||
"version": "0.3.0",
|
||||
"description": "[DEPRECATED] Use twenty-sdk instead: https://www.npmjs.com/package/twenty-sdk",
|
||||
"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__/**"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "echo 'use npx nx build'",
|
||||
"start": "echo 'deprecated'"
|
||||
"dev": "tsx src/cli.ts",
|
||||
"start": "node dist/cli.js"
|
||||
},
|
||||
"license": "AGPL-3.0"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "twenty-cli",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"tags": ["scope:cli"],
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"dependsOn": ["before-build"]
|
||||
},
|
||||
"dev": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-cli",
|
||||
"command": "tsx src/cli.ts"
|
||||
}
|
||||
},
|
||||
"start": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-cli",
|
||||
"command": "node dist/cli.js"
|
||||
}
|
||||
},
|
||||
"typecheck": {},
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type TwentyConfig } from '../../../types/config.types';
|
||||
import { TwentyConfig } from '../../../types/config.types';
|
||||
|
||||
export const testConfig: TwentyConfig = {
|
||||
apiUrl: 'http://localhost:3000',
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export default async () =>
|
||||
new Promise<void>((resolve) => {
|
||||
export default async function globalTeardown() {
|
||||
return new Promise<void>((resolve) => {
|
||||
exec('pkill -f "nest start" || true', (error: unknown) => {
|
||||
if (error) {
|
||||
console.log('No server processes to kill');
|
||||
@@ -11,3 +11,4 @@ export default async () =>
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
+4
-2
@@ -1,9 +1,11 @@
|
||||
import path from 'path';
|
||||
|
||||
export const getTestedApplicationPath = (relativePath: string): string => {
|
||||
const currentFileDir = __dirname;
|
||||
|
||||
const twentyAppsPath = path.resolve(
|
||||
__dirname,
|
||||
'../../../../../../twenty-apps',
|
||||
currentFileDir,
|
||||
'../../../../../twenty-apps',
|
||||
);
|
||||
|
||||
return path.join(twentyAppsPath, relativePath);
|
||||
@@ -1,10 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
import chalk from 'chalk';
|
||||
import { Command, CommanderError } from 'commander';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { AppCommand } from './commands/app.command';
|
||||
import { AuthCommand } from './commands/auth.command';
|
||||
import { ConfigService } from './services/config.service';
|
||||
import packageJson from '../../package.json';
|
||||
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(join(__dirname, '../package.json'), 'utf-8'),
|
||||
);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
+5
-5
@@ -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 { getFunctionBaseFile } from '../utils/get-function-base-file';
|
||||
import { getServerlessFunctionBaseFile } from '../utils/get-serverless-function-base-file';
|
||||
import { convertToLabel } from '../utils/convert-to-label';
|
||||
|
||||
export enum SyncableEntity {
|
||||
AGENT = 'agent',
|
||||
OBJECT = 'object',
|
||||
FUNCTION = 'function',
|
||||
SERVERLESS_FUNCTION = 'serverlessFunction',
|
||||
}
|
||||
|
||||
export const isSyncableEntity = (value: string): value is SyncableEntity => {
|
||||
@@ -44,12 +44,12 @@ export class AppAddCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity === SyncableEntity.FUNCTION) {
|
||||
if (entity === SyncableEntity.SERVERLESS_FUNCTION) {
|
||||
const entityName = await this.getEntityName(entity);
|
||||
|
||||
const objectFileName = `${camelcase(entityName)}.ts`;
|
||||
|
||||
const decoratedServerlessFunction = getFunctionBaseFile({
|
||||
const decoratedServerlessFunction = getServerlessFunctionBaseFile({
|
||||
name: entityName,
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ export class AppAddCommand {
|
||||
name: 'entity',
|
||||
message: `What entity do you want to create?`,
|
||||
default: '',
|
||||
choices: [SyncableEntity.FUNCTION, SyncableEntity.OBJECT],
|
||||
choices: [SyncableEntity.SERVERLESS_FUNCTION, SyncableEntity.OBJECT],
|
||||
},
|
||||
]);
|
||||
|
||||
+4
-15
@@ -1,11 +1,10 @@
|
||||
import chalk from 'chalk';
|
||||
import * as chokidar from 'chokidar';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory';
|
||||
import { ApiService } from '@/cli/services/api.service';
|
||||
import { loadManifest } from '@/cli/utils/load-manifest';
|
||||
import { AppSyncCommand } from './app-sync.command';
|
||||
|
||||
export class AppDevCommand {
|
||||
private apiService = new ApiService();
|
||||
private syncCommand = new AppSyncCommand();
|
||||
|
||||
async execute(options: {
|
||||
appPath?: string;
|
||||
@@ -18,7 +17,7 @@ export class AppDevCommand {
|
||||
|
||||
this.logStartupInfo(appPath, debounceMs);
|
||||
|
||||
await this.synchronize(appPath);
|
||||
await this.syncCommand.execute(appPath);
|
||||
|
||||
const watcher = this.setupFileWatcher(appPath, debounceMs);
|
||||
|
||||
@@ -32,16 +31,6 @@ export class AppDevCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async synchronize(appPath: string) {
|
||||
const { manifest, packageJson, yarnLock } = await loadManifest(appPath);
|
||||
|
||||
await this.apiService.syncApplication({
|
||||
manifest,
|
||||
packageJson,
|
||||
yarnLock,
|
||||
});
|
||||
}
|
||||
|
||||
private logStartupInfo(appPath: string, debounceMs: number): void {
|
||||
console.log(chalk.blue('🚀 Starting Twenty Application Development Mode'));
|
||||
console.log(chalk.gray(`📁 App Path: ${appPath}`));
|
||||
@@ -68,7 +57,7 @@ export class AppDevCommand {
|
||||
timeout = setTimeout(async () => {
|
||||
console.log(chalk.blue('🔄 Changes detected, syncing...'));
|
||||
|
||||
await this.synchronize(appPath);
|
||||
await this.syncCommand.execute(appPath);
|
||||
|
||||
console.log(
|
||||
chalk.gray('👀 Watching for changes... (Press Ctrl+C to stop)'),
|
||||
+8
-16
@@ -2,15 +2,11 @@ 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 { copyBaseApplicationProject } from '../utils/app-template';
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { convertToLabel } from '@/utils/convert-to-label';
|
||||
import { tryGitInit } from '@/utils/try-git-init';
|
||||
import { install } from '@/utils/install';
|
||||
import { convertToLabel } from '../utils/convert-to-label';
|
||||
|
||||
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
|
||||
|
||||
export class CreateAppCommand {
|
||||
export class AppInitCommand {
|
||||
async execute(directory?: string): Promise<void> {
|
||||
try {
|
||||
const { appName, appDisplayName, appDirectory, appDescription } =
|
||||
@@ -29,10 +25,6 @@ export class CreateAppCommand {
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
await install(appDirectory);
|
||||
|
||||
await tryGitInit(appDirectory);
|
||||
|
||||
this.logSuccess(appDirectory);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -86,8 +78,8 @@ export class CreateAppCommand {
|
||||
const appDescription = description.trim();
|
||||
|
||||
const appDirectory = directory
|
||||
? path.join(CURRENT_EXECUTION_DIRECTORY, directory)
|
||||
: path.join(CURRENT_EXECUTION_DIRECTORY, kebabCase(appName));
|
||||
? path.join(process.cwd(), kebabCase(directory))
|
||||
: path.join(process.cwd(), kebabCase(appName));
|
||||
|
||||
return { appName, appDisplayName, appDirectory, appDescription };
|
||||
}
|
||||
@@ -119,10 +111,10 @@ export class CreateAppCommand {
|
||||
}
|
||||
|
||||
private logSuccess(appDirectory: string): void {
|
||||
console.log(chalk.green('✅ Application created!'));
|
||||
console.log(chalk.green('✅ Application created successfully!'));
|
||||
console.log('');
|
||||
console.log(chalk.blue('Next steps:'));
|
||||
console.log(`cd ${appDirectory.split('/').reverse()[0] ?? ''}`);
|
||||
console.log('yarn auth');
|
||||
console.log(` cd ${appDirectory}`);
|
||||
console.log(' twenty app dev');
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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 { type ApiResponse } from '../types/config.types';
|
||||
import { ApiResponse } from '../types/config.types';
|
||||
import { loadManifest } from '../utils/load-manifest';
|
||||
|
||||
export class AppSyncCommand {
|
||||
@@ -49,7 +49,7 @@ export class AppSyncCommand {
|
||||
});
|
||||
}
|
||||
|
||||
if (serverlessSyncResult.success === false) {
|
||||
if (!serverlessSyncResult.success) {
|
||||
console.error(
|
||||
chalk.red('❌ Serverless functions Sync failed:'),
|
||||
serverlessSyncResult.error,
|
||||
+2
-2
@@ -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 { type ApiResponse } from '../types/config.types';
|
||||
import { 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 === false) {
|
||||
if (!result.success) {
|
||||
console.error(chalk.red('❌ Uninstall failed:'), result.error);
|
||||
} else {
|
||||
console.log(chalk.green('✅ Application uninstalled successfully'));
|
||||
+17
-28
@@ -7,18 +7,18 @@ 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';
|
||||
import { AppLogsCommand } from '@/cli/commands/app-logs.command';
|
||||
|
||||
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();
|
||||
private logsCommand = new AppLogsCommand();
|
||||
|
||||
getCommand(): Command {
|
||||
const appCommand = new Command('app');
|
||||
@@ -84,6 +84,21 @@ 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.')
|
||||
@@ -112,32 +127,6 @@ export class AppCommand {
|
||||
await this.generateCommand.execute(formatPath(appPath));
|
||||
});
|
||||
|
||||
appCommand
|
||||
.command('logs [appPath]')
|
||||
.option(
|
||||
'-u, --functionUniversalIdentifier <functionUniversalIdentifier>',
|
||||
'Only show logs for the function with this universal ID',
|
||||
)
|
||||
.option(
|
||||
'-n, --functionName <functionName>',
|
||||
'Only show logs for the function with this name',
|
||||
)
|
||||
.description('Watch application function logs')
|
||||
.action(
|
||||
async (
|
||||
appPath?: string,
|
||||
options?: {
|
||||
functionUniversalIdentifier?: string;
|
||||
functionName?: string;
|
||||
},
|
||||
) => {
|
||||
await this.logsCommand.execute({
|
||||
...options,
|
||||
appPath: formatPath(appPath),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return appCommand;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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
|
||||
@@ -0,0 +1,5 @@
|
||||
# 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.
@@ -0,0 +1,15 @@
|
||||
# {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
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
.yarn/install-state.gz
|
||||
.env
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -11,7 +11,7 @@
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"allowUnreachableCode": false,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": false,
|
||||
@@ -22,5 +22,6 @@
|
||||
"skipDefaultLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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
|
||||
@@ -0,0 +1,8 @@
|
||||
import { join } from 'path';
|
||||
|
||||
const BASE_PATH = join(__dirname, '../constants');
|
||||
|
||||
export const BASE_APPLICATION_PROJECT_PATH = join(
|
||||
BASE_PATH,
|
||||
'base-application-project',
|
||||
);
|
||||
-50
@@ -5,7 +5,6 @@ import {
|
||||
getIntrospectionQuery,
|
||||
printSchema,
|
||||
} from 'graphql/index';
|
||||
import { createClient } from 'graphql-sse';
|
||||
import {
|
||||
type ApiResponse,
|
||||
type AppManifest,
|
||||
@@ -236,53 +235,4 @@ export class ApiService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async subscribeToLogs({
|
||||
applicationUniversalIdentifier,
|
||||
functionUniversalIdentifier,
|
||||
functionName,
|
||||
}: {
|
||||
applicationUniversalIdentifier: string;
|
||||
functionUniversalIdentifier?: string;
|
||||
functionName?: string;
|
||||
}) {
|
||||
const twentyConfig = await this.configService.getConfig();
|
||||
|
||||
const wsClient = createClient({
|
||||
url: twentyConfig.apiUrl + '/graphql',
|
||||
headers: {
|
||||
Authorization: `Bearer ${twentyConfig.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/event-stream',
|
||||
},
|
||||
});
|
||||
|
||||
const query = `
|
||||
subscription SubscribeToLogs($input: ServerlessFunctionLogsInput!) {
|
||||
serverlessFunctionLogs(input: $input) {
|
||||
logs
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = {
|
||||
input: {
|
||||
applicationUniversalIdentifier,
|
||||
universalIdentifier: functionUniversalIdentifier,
|
||||
name: functionName,
|
||||
},
|
||||
};
|
||||
|
||||
wsClient.subscribe<{ serverlessFunctionLogs: { logs: string } }>(
|
||||
{
|
||||
query,
|
||||
variables,
|
||||
},
|
||||
{
|
||||
next: ({ data }) => console.log(data?.serverlessFunctionLogs.logs),
|
||||
error: (err: unknown) => console.error(err),
|
||||
complete: () => console.log('Completed'),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { type TwentyConfig } from '../types/config.types';
|
||||
import { 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] || { apiUrl: '' };
|
||||
const currentProfile = raw.profiles[profile] || {};
|
||||
|
||||
raw.profiles[profile] = { ...currentProfile, ...config };
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ describe('getObjectDecoratedClass', () => {
|
||||
name: 'MyNewObject',
|
||||
}),
|
||||
).toBe(
|
||||
`import { Object } from 'twenty-sdk';
|
||||
`import { Object } from 'twenty-sdk/application';
|
||||
|
||||
@Object({
|
||||
universalIdentifier: '4122a047-260f-4cf1-bf4f-a268579d7ddf',
|
||||
+6
-5
@@ -1,13 +1,14 @@
|
||||
import { getFunctionBaseFile } from '../get-function-base-file';
|
||||
import { getServerlessFunctionBaseFile } from '../get-serverless-function-base-file';
|
||||
|
||||
describe('getFunctionBaseFile', () => {
|
||||
describe('getServerlessFunctionBaseFile', () => {
|
||||
it('should render proper file', () => {
|
||||
expect(
|
||||
getFunctionBaseFile({
|
||||
getServerlessFunctionBaseFile({
|
||||
name: 'serverless-function-name',
|
||||
universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4',
|
||||
}),
|
||||
).toBe(`import { type FunctionConfig } from 'twenty-sdk';
|
||||
)
|
||||
.toBe(`import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
|
||||
export const main = async (params: {
|
||||
a: string;
|
||||
@@ -22,7 +23,7 @@ export const main = async (params: {
|
||||
return { message };
|
||||
};
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4',
|
||||
name: 'serverless-function-name',
|
||||
timeoutSeconds: 5,
|
||||
+22
-97
@@ -1,8 +1,8 @@
|
||||
import { ensureDirSync, writeFileSync, removeSync } from 'fs-extra';
|
||||
import { ensureDirSync, removeSync, writeFileSync } 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' {
|
||||
declare module 'twenty-sdk/application' {
|
||||
export type SyncableEntityOptions = { universalIdentifier: string };
|
||||
|
||||
type ApplicationVariable = SyncableEntityOptions & {
|
||||
@@ -58,7 +58,7 @@ declare module 'twenty-sdk' {
|
||||
type ServerlessFunctionTrigger = SyncableEntityOptions &
|
||||
(RouteTrigger | CronTrigger | DatabaseEventTrigger);
|
||||
|
||||
export type FunctionConfig = SyncableEntityOptions & {
|
||||
export type ServerlessFunctionConfig = SyncableEntityOptions & {
|
||||
name?: string;
|
||||
description?: string;
|
||||
timeoutSeconds?: number;
|
||||
@@ -93,13 +93,13 @@ declare module 'twenty-sdk' {
|
||||
`;
|
||||
|
||||
const serverlessFunctionMock = `
|
||||
import { type FunctionConfig } from 'twenty-sdk';
|
||||
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
|
||||
export const main = async (params: any): Promise<any> => {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
|
||||
name: 'hello',
|
||||
timeoutSeconds: 2,
|
||||
@@ -129,7 +129,7 @@ const objectMock = `import {
|
||||
BaseObjectMetadata,
|
||||
FieldMetadata,
|
||||
FieldMetadataType
|
||||
} from 'twenty-sdk';
|
||||
} from 'twenty-sdk/application';
|
||||
|
||||
enum PostCardStatus {
|
||||
DRAFT = 'DRAFT',
|
||||
@@ -215,97 +215,19 @@ 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 appDirectory = join(tmpdir(), 'test-app');
|
||||
const appName = 'my-app';
|
||||
const appDisplayName = 'My App';
|
||||
const appDescription = 'My app description';
|
||||
const appDirectory = join(tmpdir(), 'twenty-manifest-');
|
||||
|
||||
beforeEach(async () => {
|
||||
await ensureDirSync(appDirectory);
|
||||
|
||||
write(appDirectory, 'yarn.lock', yarnLockMock);
|
||||
|
||||
write(appDirectory, 'application.config.ts', applicationConfigMock);
|
||||
|
||||
write(
|
||||
await copyBaseApplicationProject({
|
||||
appName,
|
||||
appDisplayName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
'tsconfig.json',
|
||||
JSON.stringify(tsConfigJsonMock, null, 2),
|
||||
);
|
||||
|
||||
write(
|
||||
appDirectory,
|
||||
'package.json',
|
||||
JSON.stringify(packageJsonMock, null, 2),
|
||||
);
|
||||
});
|
||||
|
||||
write(appDirectory, 'src/Account.ts', objectMock);
|
||||
|
||||
@@ -336,11 +258,10 @@ 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 IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.',
|
||||
);
|
||||
expect(yarnLock).toContain('# This file is generated by running ');
|
||||
|
||||
// application
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { universalIdentifier: _, ...otherInfo } = manifest.application;
|
||||
expect(otherInfo).toEqual({
|
||||
displayName: 'My App',
|
||||
@@ -350,6 +271,7 @@ 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',
|
||||
@@ -419,7 +341,9 @@ 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
|
||||
@@ -432,6 +356,7 @@ 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':
|
||||
@@ -0,0 +1,107 @@
|
||||
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',
|
||||
);
|
||||
};
|
||||
+2
-6
@@ -1,13 +1,9 @@
|
||||
import {
|
||||
type Diagnostic,
|
||||
formatDiagnosticsWithColorAndContext,
|
||||
sys,
|
||||
} from 'typescript';
|
||||
import ts, { formatDiagnosticsWithColorAndContext, sys } from 'typescript';
|
||||
|
||||
export const formatAndWarnTsDiagnostics = ({
|
||||
diagnostics,
|
||||
}: {
|
||||
diagnostics: Diagnostic[];
|
||||
diagnostics: ts.Diagnostic[];
|
||||
}) => {
|
||||
if (diagnostics.length > 0) {
|
||||
const formattedDiagnostics = formatDiagnosticsWithColorAndContext(
|
||||
+1
-1
@@ -15,7 +15,7 @@ export const getObjectDecoratedClass = ({
|
||||
|
||||
const className = camelCaseName[0].toUpperCase() + camelCaseName.slice(1);
|
||||
|
||||
return `import { Object } from 'twenty-sdk';
|
||||
return `import { Object } from 'twenty-sdk/application';
|
||||
|
||||
@Object({
|
||||
${decoratorOptions}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const getFunctionBaseFile = ({
|
||||
export const getServerlessFunctionBaseFile = ({
|
||||
name,
|
||||
universalIdentifier = v4(),
|
||||
}: {
|
||||
@@ -10,7 +10,7 @@ export const getFunctionBaseFile = ({
|
||||
}) => {
|
||||
const kebabCaseName = kebabCase(name);
|
||||
|
||||
return `import { type FunctionConfig } from 'twenty-sdk';
|
||||
return `import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
|
||||
export const main = async (params: {
|
||||
a: string;
|
||||
@@ -25,7 +25,7 @@ export const main = async (params: {
|
||||
return { message };
|
||||
};
|
||||
|
||||
export const config: FunctionConfig = {
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
universalIdentifier: '${universalIdentifier}',
|
||||
name: '${kebabCaseName}',
|
||||
timeoutSeconds: 5,
|
||||
+2
-3
@@ -1,3 +1,4 @@
|
||||
import ts from 'typescript';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
createProgram,
|
||||
@@ -5,8 +6,6 @@ import {
|
||||
parseJsonConfigFileContent,
|
||||
readConfigFile,
|
||||
sys,
|
||||
type Program,
|
||||
type Diagnostic,
|
||||
} from 'typescript';
|
||||
|
||||
const getProgramFromTsconfig = ({
|
||||
@@ -42,7 +41,7 @@ export const getTsProgramAndDiagnostics = async ({
|
||||
appPath,
|
||||
}: {
|
||||
appPath: string;
|
||||
}): Promise<{ program: Program; diagnostics: Diagnostic[] }> => {
|
||||
}): Promise<{ program: ts.Program; diagnostics: ts.Diagnostic[] }> => {
|
||||
const program = getProgramFromTsconfig({
|
||||
appPath,
|
||||
tsconfigPath: 'tsconfig.json',
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { type ParseError, parse as parseJsonc } from 'jsonc-parser';
|
||||
import { ParseError, parse as parseJsonc } from 'jsonc-parser';
|
||||
|
||||
export interface JsoncParseOptions {
|
||||
allowTrailingComma?: boolean;
|
||||
+19
-19
@@ -1,15 +1,15 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { posix, relative, sep } from 'path';
|
||||
import {
|
||||
type Decorator,
|
||||
type Expression,
|
||||
type FunctionDeclaration,
|
||||
type Modifier,
|
||||
type Node,
|
||||
type Program,
|
||||
type SourceFile,
|
||||
Decorator,
|
||||
Expression,
|
||||
FunctionDeclaration,
|
||||
Modifier,
|
||||
Node,
|
||||
Program,
|
||||
SourceFile,
|
||||
SyntaxKind,
|
||||
type VariableDeclaration,
|
||||
VariableDeclaration,
|
||||
forEachChild,
|
||||
getDecorators,
|
||||
isArrayLiteralExpression,
|
||||
@@ -34,13 +34,13 @@ import {
|
||||
} from 'typescript';
|
||||
import { GENERATED_FOLDER_NAME } from '../services/generate.service';
|
||||
import {
|
||||
type AppManifest,
|
||||
type Application,
|
||||
type FieldMetadata,
|
||||
type ObjectManifest,
|
||||
type PackageJson,
|
||||
type ServerlessFunctionManifest,
|
||||
type Sources,
|
||||
AppManifest,
|
||||
Application,
|
||||
FieldMetadata,
|
||||
ObjectManifest,
|
||||
PackageJson,
|
||||
ServerlessFunctionManifest,
|
||||
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 FunctionConfig)
|
||||
* - one must be `config` (typed ServerlessFunctionConfig)
|
||||
* - 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 FunctionConfig if present
|
||||
// (Light) type guard: ensure declared type mentions ServerlessFunctionConfig if present
|
||||
const maybeVarDecl = configExport.declNode as VariableDeclaration;
|
||||
if ('type' in maybeVarDecl && maybeVarDecl.type) {
|
||||
const typeText = maybeVarDecl.type.getText(sf);
|
||||
if (!/\bFunctionConfig\b/.test(typeText)) {
|
||||
if (!/\bServerlessFunctionConfig\b/.test(typeText)) {
|
||||
throw new Error(
|
||||
`"config" in ${sf.fileName} must be typed as FunctionConfig (got: ${typeText}).`,
|
||||
`"config" in ${sf.fileName} must be typed as ServerlessFunctionConfig (got: ${typeText}).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
-8
@@ -1,23 +1,31 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"outDir": "./dist-e2e",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": false,
|
||||
"sourceMap": true
|
||||
"declarationMap": false,
|
||||
"sourceMap": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/**/__tests__/**/*.e2e-spec.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"dist-e2e",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/__tests__/**"
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
|
||||
}
|
||||
@@ -1,24 +1,21 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"target": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": false,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/*.e2e-spec.ts",
|
||||
"**/__tests__/**"
|
||||
]
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/**/__tests__/**/*.spec.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
]
|
||||
}
|
||||
@@ -39,12 +39,6 @@ Deploy Twenty on Railway with the community maintained template below.
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Twenty on Sealos
|
||||
|
||||
Deploy Twenty on Sealos with the community maintained template below.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Others
|
||||
|
||||
Please feel free to Open a PR to add more Cloud Provider options.
|
||||
|
||||
@@ -39,12 +39,6 @@ crwdns60692:0crwdne60692:0
|
||||
|
||||
crwdns60694:0https://railway.com/button.svgcrwdnd60694:0https://railway.com/deploy/nAL3hAcrwdne60694:0
|
||||
|
||||
### crwdns64878:0crwdne64878:0
|
||||
|
||||
crwdns64880:0crwdne64880:0
|
||||
|
||||
crwdns64882:0https://sealos.io/Deploy-on-Sealos.svgcrwdnd64882:0https://sealos.io/products/app-store/twentycrwdne64882:0
|
||||
|
||||
## crwdns60696:0crwdne60696:0
|
||||
|
||||
crwdns60698:0crwdne60698:0
|
||||
|
||||
@@ -39,12 +39,6 @@ Stel Twenty op Railway in met die gemeenskap-onderhoudene sjabloon hieronder.
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Ander
|
||||
|
||||
Stel Twenty op Sealos in met die deur die gemeenskap onderhoue sjabloon hieronder.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Ander
|
||||
|
||||
Voel vry om 'n PR te open om meer Wolkverskaffer opsies by te voeg.
|
||||
|
||||
@@ -57,7 +57,7 @@ sectionInfo: أتمتة العمليات والاندماج مع الأدوات
|
||||
|
||||
**المشكلة**: الوصول إلى حد 100 سير عمل متزامن لكل مساحة عمل.
|
||||
|
||||
<Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning>لا يمكنك تشغيل أكثر من 100 سير عمل بالتوازي في أي وقت لكل مساحة عمل.</Warning></Warning>
|
||||
<Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning>لا يمكنك تشغيل أكثر من 100 سير عمل بالتوازي في أي وقت لكل مساحة عمل.</Warning></Warning>
|
||||
|
||||
**حلول**:
|
||||
|
||||
|
||||
@@ -39,12 +39,6 @@ Nasazení Twenty na Railway s komunitně udržovanou šablonou níže.
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Ostatní
|
||||
|
||||
Nasazení Twenty na Sealos s komunitně udržovanou šablonou níže.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Ostatní
|
||||
|
||||
Neváhejte otevřít PR pro přidání dalších možností poskytovatele cloudových služeb.
|
||||
|
||||
@@ -39,12 +39,6 @@ Implementer Twenty på Railway med den fællesskabsvedligeholdte skabelon nedenf
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Twenty på Sealos
|
||||
|
||||
Implementer Twenty på Sealos med den fællesskabsvedligeholdte skabelon nedenfor.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Andre
|
||||
|
||||
Du er velkommen til at åbne en PR for at tilføje flere Cloud Provider-muligheder.
|
||||
|
||||
@@ -39,12 +39,6 @@ image: /images/user-guide/notes/notes_header.png
|
||||
|
||||
[](https://railway.com/deploy/nAL3hA)
|
||||
|
||||
### Twenty στο Sealos
|
||||
|
||||
Αναπτύξτε το Twenty στο Sealos με το υποστηριζόμενο από την κοινότητα πρότυπο παρακάτω.
|
||||
|
||||
[](https://sealos.io/products/app-store/twenty)
|
||||
|
||||
## Άλλες επιλογές
|
||||
|
||||
Παρακαλώ μη διστάσετε να ανοίξετε ένα PR για να προσθέσετε περισσότερες επιλογές Παρόχου Cloud.
|
||||
|
||||
@@ -18,7 +18,7 @@ Absolutamente. Puedes crear un nuevo espacio de trabajo haciendo clic en el men
|
||||
|
||||
<Accordion title="I accidentally created multiple workspaces but only need one. What should I do?"><Accordion title="How do I change my workspace appearance and regional settings?">Ve a `Configuración → Experiencia` para personalizar:
|
||||
|
||||
<Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning>No elimines tu cuenta (accesible en Configuración → Configuración de perfil): tu cuenta se comparte entre los diferentes espacios de trabajo.</Warning></Warning>
|
||||
<Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning>No elimines tu cuenta (accesible en Configuración → Configuración de perfil): tu cuenta se comparte entre los diferentes espacios de trabajo.</Warning></Warning>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="How can I disable my workspace?"><Accordion title="How can I disable my workspace?"><Accordion title="How can I disable my workspace?"><Accordion title="How can I disable my workspace?"><Accordion title="How can I disable my workspace?">Si solo deseas desactivar tu espacio de trabajo (no eliminarlo), ve a `Configuración → Facturación` y haz clic en `Cancelar plan`.</Accordion></Accordion>
|
||||
|
||||
@@ -57,7 +57,7 @@ Los formularios están diseñados actualmente solo para disparadores manuales. P
|
||||
|
||||
**Problema**: Se alcanza el límite de 100 flujos de trabajo concurrentes por espacio de trabajo.
|
||||
|
||||
<Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning>No puedes ejecutar más de 100 flujos de trabajo en paralelo en cualquier momento por espacio de trabajo.</Warning></Warning>
|
||||
<Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning><Warning>No puedes ejecutar más de 100 flujos de trabajo en paralelo en cualquier momento por espacio de trabajo.</Warning></Warning>
|
||||
|
||||
**Soluciones**:
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user