diff --git a/packages/create-twenty-app/README.md b/packages/create-twenty-app/README.md index 16093688c1..ce7cd052f5 100644 --- a/packages/create-twenty-app/README.md +++ b/packages/create-twenty-app/README.md @@ -19,7 +19,7 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T - Strong TypeScript support and typed client generation ## Prerequisites -- Node.js 18+ (recommended) and Yarn 4 +- 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 diff --git a/packages/create-twenty-app/project.json b/packages/create-twenty-app/project.json index 98cb0d006d..3692d42fb5 100644 --- a/packages/create-twenty-app/project.json +++ b/packages/create-twenty-app/project.json @@ -4,7 +4,7 @@ "projectType": "library", "tags": ["scope:create-app"], "targets": { - "build": { + "before-build": { "executor": "nx:run-commands", "cache": true, "options": { @@ -13,6 +13,19 @@ }, "dependsOn": ["^build", "typecheck"] }, + "build": { + "executor": "nx:run-commands", + "cache": true, + "options": { + "cwd": "packages/create-twenty-app", + "commands": [ + "mkdir -p dist/constants/base-application", + "cp -R src/constants/base-application dist/constants", + "cp -R ../../.yarn/releases dist/constants/base-application/.yarn" + ] + }, + "dependsOn": ["before-build"] + }, "dev": { "executor": "nx:run-commands", "dependsOn": ["build"], diff --git a/packages/create-twenty-app/src/constants/base-application/.nvmrc b/packages/create-twenty-app/src/constants/base-application/.nvmrc new file mode 100644 index 0000000000..341cb50613 --- /dev/null +++ b/packages/create-twenty-app/src/constants/base-application/.nvmrc @@ -0,0 +1 @@ +24.5.0 diff --git a/packages/create-twenty-app/src/constants/base-application/.yarnrc.yml b/packages/create-twenty-app/src/constants/base-application/.yarnrc.yml new file mode 100644 index 0000000000..fda2aea431 --- /dev/null +++ b/packages/create-twenty-app/src/constants/base-application/.yarnrc.yml @@ -0,0 +1,3 @@ +yarnPath: .yarn/releases/yarn-4.9.2.cjs + +nodeLinker: node-modules diff --git a/packages/create-twenty-app/src/constants/base-application/README.md b/packages/create-twenty-app/src/constants/base-application/README.md new file mode 100644 index 0000000000..58bc619f4e --- /dev/null +++ b/packages/create-twenty-app/src/constants/base-application/README.md @@ -0,0 +1,35 @@ +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, run the development server: + +```bash +yarn dev +``` + +Open your Twenty instance and go to `/settings/applications` section to see the result. + +You can start adding twenty entities by running + +```bash +yarn create-entity +``` + +The application auto-updates as you edit the file. + +## 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! diff --git a/packages/create-twenty-app/src/constants/base-application/eslint.config.mjs b/packages/create-twenty-app/src/constants/base-application/eslint.config.mjs new file mode 100644 index 0000000000..e10679df19 --- /dev/null +++ b/packages/create-twenty-app/src/constants/base-application/eslint.config.mjs @@ -0,0 +1,137 @@ +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', + }, + }, +]; diff --git a/packages/create-twenty-app/src/constants/base-application/tsconfig.json b/packages/create-twenty-app/src/constants/base-application/tsconfig.json new file mode 100644 index 0000000000..92a2a7a90a --- /dev/null +++ b/packages/create-twenty-app/src/constants/base-application/tsconfig.json @@ -0,0 +1,27 @@ +{ + "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"] +} diff --git a/packages/create-twenty-app/src/create-app.command.ts b/packages/create-twenty-app/src/create-app.command.ts index 06f7bf5bef..15d9b1ac46 100644 --- a/packages/create-twenty-app/src/create-app.command.ts +++ b/packages/create-twenty-app/src/create-app.command.ts @@ -2,16 +2,14 @@ import chalk from 'chalk'; import * as fs from 'fs-extra'; import inquirer from 'inquirer'; import * as path from 'path'; -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 { tryGitInit } from './utils/try-git-init'; +import { install } from './utils/install'; const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd(); -const execPromise = promisify(exec); - export class CreateAppCommand { async execute(directory?: string): Promise { try { @@ -31,18 +29,11 @@ export class CreateAppCommand { 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 install(appDirectory); - await this.logSuccess(appDirectory); + await tryGitInit(appDirectory); + + this.logSuccess(appDirectory); } catch (error) { console.error( chalk.red('Initialization failed:'), @@ -128,10 +119,10 @@ export class CreateAppCommand { } private logSuccess(appDirectory: string): void { - console.log(chalk.green('✅ Application created successfully!')); + console.log(chalk.green('✅ Application created!')); console.log(''); console.log(chalk.blue('Next steps:')); - console.log(` cd ${appDirectory.split('/').reverse()[0] ?? ''}`); - console.log(' twenty app dev'); + console.log(`cd ${appDirectory.split('/').reverse()[0] ?? ''}`); + console.log('twenty app dev'); } } diff --git a/packages/create-twenty-app/src/utils/app-template.ts b/packages/create-twenty-app/src/utils/app-template.ts index 265832b913..108a6f3fda 100644 --- a/packages/create-twenty-app/src/utils/app-template.ts +++ b/packages/create-twenty-app/src/utils/app-template.ts @@ -13,27 +13,19 @@ export const copyBaseApplicationProject = async ({ appDescription: string; appDirectory: string; }) => { + await fs.copy(join(__dirname, '../constants/base-application'), appDirectory); + await createPackageJson({ appName, appDirectory }); + await createGitignore(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) => { @@ -43,55 +35,45 @@ const createYarnLock = async (appDirectory: string) => { 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. -const createYarnRc = async (appDirectory: string) => { - const yarnRcContent = `nodeLinker: node-modules +# 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, '.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', - ); + await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent); }; const createApplicationConfig = async ({ @@ -126,7 +108,7 @@ const createPackageJson = async ({ }) => { const packageJson = { name: appName, - version: '0.0.1', + version: '0.1.0', license: 'MIT', engines: { node: '^24.5.0', @@ -157,20 +139,3 @@ const createPackageJson = async ({ '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); -}; diff --git a/packages/create-twenty-app/src/utils/install.ts b/packages/create-twenty-app/src/utils/install.ts new file mode 100644 index 0000000000..d0f27d5258 --- /dev/null +++ b/packages/create-twenty-app/src/utils/install.ts @@ -0,0 +1,13 @@ +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); + } +}; diff --git a/packages/create-twenty-app/src/utils/try-git-init.ts b/packages/create-twenty-app/src/utils/try-git-init.ts new file mode 100644 index 0000000000..2c2e1257c5 --- /dev/null +++ b/packages/create-twenty-app/src/utils/try-git-init.ts @@ -0,0 +1,70 @@ +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 => { + 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 => { + try { + await execPromise('hg --cwd . root', { cwd: root }); + return true; + } catch { + // Empty catch block + } + return false; +}; + +const isDefaultBranchSet = async (root: string): Promise => { + try { + await execPromise('git config init.defaultBranch', { cwd: root }); + return true; + } catch { + // Empty catch block + } + return false; +}; + +export const tryGitInit = async (root: string): Promise => { + 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; + } +}; diff --git a/packages/twenty-sdk/README.md b/packages/twenty-sdk/README.md index beddb89350..c3d3dac14a 100644 --- a/packages/twenty-sdk/README.md +++ b/packages/twenty-sdk/README.md @@ -19,7 +19,7 @@ A CLI and SDK to develop, build, and publish applications that extend [Twenty CR - Works great with the scaffolder: [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) ## Prerequisites -- Node.js 18+ (recommended) and Yarn 4 +- Node.js 24+ (recommended) and Yarn 4 - A Twenty workspace and an API key. Generate one at https://app.twenty.com/settings/api-webhooks ## Installation