Improve application ast 3 (#17061)

- fix should generate
- fix errors not displayed properly
This commit is contained in:
martmull
2026-01-10 15:52:28 +01:00
committed by GitHub
parent 3acc87a620
commit 936ec06fe8
10 changed files with 32 additions and 11 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.3.0",
"version": "0.3.1",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -67,7 +67,7 @@ describe('copyBaseApplicationProject', () => {
const packageJson = await fs.readJson(packageJsonPath);
expect(packageJson.name).toBe('my-test-app');
expect(packageJson.version).toBe('0.1.0');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.3.0');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.3.1');
expect(packageJson.scripts.sync).toBe('twenty app sync');
expect(packageJson.scripts.dev).toBe('twenty app dev');
});
@@ -168,7 +168,7 @@ const createPackageJson = async ({
'lint-fix': 'eslint --fix',
},
dependencies: {
'twenty-sdk': '0.3.0',
'twenty-sdk': '0.3.1',
},
devDependencies: {
typescript: '^5.9.3',
@@ -280,6 +280,10 @@ Key points:
- The `fields` array is optional — you can define objects without custom fields.
- You can scaffold new objects using `yarn create-entity`, which guides you through naming, fields, and relationships.
<Note>
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields such as `name`, `createdAt`, `updatedAt`, `createdBy`, `position`, and `deletedAt`. You don't need to define these in your `fields` array — only add your custom fields.
</Note>
<Accordion title="Alternative: Decorator-based syntax">
You can also define objects using TypeScript decorators. This approach uses class-based syntax with `@Object`, `@Field`, and `@Relation` decorators:
@@ -2,6 +2,7 @@ import { H2Title, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { type ServerlessFunction } from '~/generated/graphql';
import { useLingui } from '@lingui/react/macro';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { SettingsDatabaseEventsForm } from '@/settings/components/SettingsDatabaseEventsForm';
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import { Table } from '@/ui/layout/table/components/Table';
@@ -122,7 +123,9 @@ export const SettingsServerlessFunctionTriggersTab = ({
{routeTriggers.map((routeTrigger, index) => (
<StyledRouteTriggerTableRow key={index}>
<StyledTableCell>
<OverflowingTextWithTooltip text={routeTrigger.path} />
<OverflowingTextWithTooltip
text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
/>
</StyledTableCell>
<StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
<StyledTableCell>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-sdk",
"version": "0.3.0",
"version": "0.3.1",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
@@ -1 +1,7 @@
export const testFunction2 = () => 'testFunction2';
export const testFunction2 = () => {
const Twenty = require('../../generated').default;
const client = new Twenty();
return client.query('testQuery');
};
@@ -1,3 +1,4 @@
import { inspect } from 'util';
import chalk from 'chalk';
import { Command } from 'commander';
import {
@@ -12,6 +13,8 @@ import { formatPath } from '@/cli/utils/format-path';
import { AppGenerateCommand } from '@/cli/commands/app-generate.command';
import { AppLogsCommand } from '@/cli/commands/app-logs.command';
inspect.defaultOptions.depth = 10;
export class AppCommand {
private devCommand = new AppDevCommand();
private syncCommand = new AppSyncCommand();
@@ -154,7 +154,7 @@ describe('loadManifest with test-app', () => {
expect(appSources['test-function.function.ts']).toContain('defineFunction');
expect(appSources['default-function.role.ts']).toContain('defineRole');
expect(shouldGenerate).toBe(false);
expect(shouldGenerate).toBe(true);
const expectedRoleId = DEFAULT_FUNCTION_ROLE_UNIVERSAL_IDENTIFIER;
@@ -181,19 +181,24 @@ const loadSources = async (appPath: string): Promise<Sources> => {
/**
* Check if the app imports from the generated folder.
* Detects any `import ... from '...generated'` or `import ... from '...generated/...'` pattern.
* Detects ESM imports: `import ... from '...generated'` or `import ... from '...generated/...'`
* Detects CommonJS requires: `require('...generated')` or `require('...generated/...')`
*/
const checkShouldGenerate = async (appPath: string): Promise<boolean> => {
const tsFiles = await loadFiles(['src/**/*.ts'], appPath);
// Matches: import ... from 'generated' or from '.../generated' or from '.../generated/...'
const generatedImportPattern =
// Matches ESM: import ... from 'generated' or from '.../generated' or from '.../generated/...'
const esmImportPattern =
/from\s+['"][^'"]*\/generated(?:\/[^'"]*)?['"]|from\s+['"]generated['"]/;
// Matches CommonJS: require('generated') or require('.../generated') or require('.../generated/...')
const commonJsRequirePattern =
/require\s*\(\s*['"][^'"]*\/generated(?:\/[^'"]*)?['"]\s*\)|require\s*\(\s*['"]generated['"]\s*\)/;
for (const filepath of tsFiles) {
const content = await fs.readFile(filepath, 'utf8');
if (generatedImportPattern.test(content)) {
if (esmImportPattern.test(content) || commonJsRequirePattern.test(content)) {
return true;
}
}