import * as fs from 'fs-extra'; import { join } from 'path'; import { v4 } from 'uuid'; import { ASSETS_DIR } from 'twenty-shared/application'; const SRC_FOLDER = 'src'; 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 createPublicAssetDirectory(appDirectory); await createYarnLock(appDirectory); const sourceFolderPath = join(appDirectory, SRC_FOLDER); await fs.ensureDir(sourceFolderPath); await createDefaultRoleConfig({ displayName: appDisplayName, appDirectory: sourceFolderPath, fileFolder: 'roles', fileName: 'default-role.ts', }); await createDefaultFrontComponent({ appDirectory: sourceFolderPath, fileFolder: 'front-components', fileName: 'hello-world.tsx', }); await createDefaultFunction({ appDirectory: sourceFolderPath, fileFolder: 'logic-functions', fileName: 'hello-world.ts', }); await createApplicationConfig({ displayName: appDisplayName, description: appDescription, appDirectory: sourceFolderPath, fileName: 'application-config.ts', }); }; const createPublicAssetDirectory = async (appDirectory: string) => { await fs.ensureDir(join(appDirectory, ASSETS_DIR)); }; 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/ .twenty/* !.twenty/output/ # 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 createDefaultRoleConfig = async ({ displayName, appDirectory, fileFolder, fileName, }: { displayName: string; appDirectory: string; fileFolder?: string; fileName: string; }) => { const universalIdentifier = v4(); const content = `import { defineRole } from 'twenty-sdk'; export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = '${universalIdentifier}'; export default defineRole({ universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, label: '${displayName} default function role', description: '${displayName} default function role', canReadAllObjectRecords: true, canUpdateAllObjectRecords: true, canSoftDeleteAllObjectRecords: true, canDestroyAllObjectRecords: false, }); `; await fs.ensureDir(join(appDirectory, fileFolder ?? '')); await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content); }; const createDefaultFrontComponent = async ({ appDirectory, fileFolder, fileName, }: { appDirectory: string; fileFolder?: string; fileName: string; }) => { const universalIdentifier = v4(); const content = `import { defineFrontComponent } from 'twenty-sdk'; export const HelloWorld = () => { return (

Hello, World!

This is your first front component.

); }; export default defineFrontComponent({ universalIdentifier: '${universalIdentifier}', name: 'hello-world-front-component', description: 'A sample front component', component: HelloWorld, }); `; await fs.ensureDir(join(appDirectory, fileFolder ?? '')); await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content); }; const createDefaultFunction = async ({ appDirectory, fileFolder, fileName, }: { appDirectory: string; fileFolder?: string; fileName: string; }) => { const universalIdentifier = v4(); const content = `import { defineLogicFunction } from 'twenty-sdk'; const handler = async (): Promise<{ message: string }> => { return { message: 'Hello, World!' }; }; // Logic function handler - rename and implement your logic export default defineLogicFunction({ universalIdentifier: '${universalIdentifier}', name: 'hello-world-logic-function', description: 'A simple logic function', timeoutSeconds: 5, handler, httpRouteTriggerSettings: { path: '/hello-world-logic-function', httpMethod: 'GET', isAuthRequired: false, }, }); `; await fs.ensureDir(join(appDirectory, fileFolder ?? '')); await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), content); }; const createApplicationConfig = async ({ displayName, description, appDirectory, fileFolder, fileName, }: { displayName: string; description?: string; appDirectory: string; fileFolder?: string; fileName: string; }) => { const content = `import { defineApplication } from 'twenty-sdk'; import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; export default defineApplication({ universalIdentifier: '${v4()}', displayName: '${displayName}', description: '${description ?? ''}', defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, }); `; await fs.ensureDir(join(appDirectory, fileFolder ?? '')); await fs.writeFile(join(appDirectory, fileFolder ?? '', fileName), 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: { 'auth:login': 'twenty auth:login', 'auth:logout': 'twenty auth:logout', 'auth:status': 'twenty auth:status', 'auth:switch': 'twenty auth:switch', 'auth:list': 'twenty auth:list', 'app:dev': 'twenty app:dev', 'entity:add': 'twenty entity:add', 'app:generate': 'twenty app:generate', 'function:logs': 'twenty function:logs', 'function:execute': 'twenty function:execute', 'app:uninstall': 'twenty app:uninstall', help: 'twenty help', lint: 'eslint', 'lint:fix': 'eslint --fix', }, dependencies: { 'twenty-sdk': '0.5.1', }, devDependencies: { typescript: '^5.9.3', '@types/node': '^24.7.2', '@types/react': '^18.2.0', react: '^18.2.0', eslint: '^9.32.0', 'typescript-eslint': '^8.50.0', }, }; await fs.writeFile( join(appDirectory, 'package.json'), JSON.stringify(packageJson, null, 2), 'utf8', ); };