Implement sync in dev mode (#17405)
implement orchestrator to sync application in dev mode Log example when starting dev mode, delete and add back functions ```bash 👩💻 Workspace - default [init] 🚀 Starting Twenty Application Development Mode [init] 📁 App Path: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.gitignore [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.nvmrc [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.yarnrc.yml [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/README.md [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/eslint.config.mjs [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/package.json [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/tsconfig.json [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/yarn.lock [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.yarn/install-state.gz [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/ooo.front-component.tsx [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/tata.object.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/application.config.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/default-function.role.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.front-component.tsx [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/myObject.object.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/utils/toto.ts [dev-mode] Building manifest... [dev-mode] Successfully built manifest [dev-mode] Syncing... [dev-mode] ✓ Successfully built src/ooo.front-component.tsx [dev-mode] Uploading .twenty/output/src/ooo.front-component.mjs... [dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world.front-component.tsx [dev-mode] Uploading .twenty/output/src/app/hello-world.front-component.mjs... [dev-mode] ✓ Successfully built src/app/hello-world-3.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-3.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world.function.mjs... [dev-mode] Successfully uploaded .twenty/output/src/ooo.front-component.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-3.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.front-component.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs [dev-mode] ✓ Synced [dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs... [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] Building manifest... [dev-mode] Successfully built manifest [dev-mode] Syncing... [dev-mode] ✓ Synced [dev-mode] Build failed: [dev-mode] Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts" [dev-mode] Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts" [dev-mode] Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts" [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts [dev-mode] Building manifest... [dev-mode] ⚠ No functions defined [dev-mode] Successfully built manifest [dev-mode] Syncing... 🗑️ Removed src/app/hello-world-2.function.mjs 🗑️ Removed src/app/hello-world-3.function.mjs 🗑️ Removed src/app/hello-world.function.mjs [dev-mode] ✓ Synced [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts [dev-mode] Building manifest... [dev-mode] Successfully built manifest [dev-mode] Syncing... [dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world-3.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-3.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world.function.mjs... [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-3.function.mjs [dev-mode] ✓ Synced ```
This commit is contained in:
+2
-1
@@ -1,9 +1,10 @@
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
|
||||
const APP_PATH = join(__dirname, '..');
|
||||
const MANIFEST_OUTPUT_PATH = join(APP_PATH, '.twenty/output/manifest.json');
|
||||
const MANIFEST_OUTPUT_PATH = join(APP_PATH, OUTPUT_DIR, 'ioi', 'manifest.json');
|
||||
|
||||
describe('invalid-app manifest', () => {
|
||||
it('should fail to build manifest due to duplicate universalIdentifier', async () => {
|
||||
|
||||
+7
-6
@@ -1,14 +1,15 @@
|
||||
import { getTestedApplicationPath } from '@/cli/__tests__/e2e/utils/get-tested-application-path.util';
|
||||
import { AppSyncCommand } from '@/cli/commands/app/app-sync';
|
||||
import { AppUninstallCommand } from '@/cli/commands/app/app-uninstall';
|
||||
import { existsSync } from 'fs';
|
||||
import { inspect } from 'util';
|
||||
import { join } from 'path';
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
|
||||
inspect.defaultOptions.depth = 10;
|
||||
|
||||
describe('Application: install delete and reinstall rich-app', () => {
|
||||
const applicationName = 'rich-app';
|
||||
const syncCommand = new AppSyncCommand();
|
||||
const deleteCommand = new AppUninstallCommand();
|
||||
const appPath = getTestedApplicationPath(applicationName);
|
||||
|
||||
@@ -26,9 +27,9 @@ describe('Application: install delete and reinstall rich-app', () => {
|
||||
});
|
||||
|
||||
it(`should successfully install ${applicationName} application`, async () => {
|
||||
const result = await syncCommand.execute(appPath);
|
||||
await runAppDev({ appPath });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(existsSync(join(appPath, OUTPUT_DIR, 'manifest.json'))).toBe(true);
|
||||
});
|
||||
|
||||
it(`should successfully delete ${applicationName} application`, async () => {
|
||||
@@ -41,8 +42,8 @@ describe('Application: install delete and reinstall rich-app', () => {
|
||||
});
|
||||
|
||||
it(`should successfully re-install ${applicationName} application`, async () => {
|
||||
const result = await syncCommand.execute(appPath);
|
||||
await runAppDev({ appPath });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(existsSync(join(appPath, OUTPUT_DIR, 'manifest.json'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { runAppBuild } from '@/cli/__tests__/integration/utils/run-app-build.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { defineManifestTests } from '../app-dev/tests/manifest.tests';
|
||||
import { defineFrontComponentsTests } from '../app-dev/tests/front-components.tests';
|
||||
import { defineFunctionsTests } from '../app-dev/tests/functions.tests';
|
||||
|
||||
const APP_PATH = join(__dirname, '../..');
|
||||
|
||||
describe('rich-app app:build', () => {
|
||||
let result: RunCliCommandResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runAppBuild({ appPath: APP_PATH });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
}, 60000);
|
||||
|
||||
defineConsoleOutputTests(() => result);
|
||||
defineManifestTests(APP_PATH);
|
||||
defineFunctionsTests(APP_PATH);
|
||||
defineFrontComponentsTests(APP_PATH);
|
||||
});
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
import { getOutputByPrefix } from '@/cli/__tests__/integration/utils/get-output-by-prefix.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
|
||||
export const defineConsoleOutputTests = (
|
||||
getResult: () => RunCliCommandResult,
|
||||
): void => {
|
||||
describe('console output', () => {
|
||||
it('should contain init messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'init');
|
||||
|
||||
expect(output).toContain('[init] 🚀 Building Twenty Application');
|
||||
expect(output).toContain('[init] 📁 App Path:');
|
||||
expect(output).toContain('[init] ✅ Build completed successfully');
|
||||
});
|
||||
|
||||
it('should contain manifest-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'manifest-watch');
|
||||
|
||||
expect(output).toContain('[manifest-watch] 🔄 Building...');
|
||||
expect(output).toContain('[manifest-watch] ✓ Loaded "Hello World"');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 2 object(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 4 function(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 4 front component(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 2 role(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Written to');
|
||||
});
|
||||
|
||||
it('should contain functions-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'functions-watch');
|
||||
|
||||
expect(output).toContain('[functions-watch] 📦 Building...');
|
||||
expect(output).toContain('[functions-watch] ✓ Built');
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
});
|
||||
|
||||
it('should not contain watching messages', () => {
|
||||
const output = getResult().output;
|
||||
|
||||
expect(output).not.toContain('👀 Watching for changes...');
|
||||
expect(output).not.toContain('📂 Watcher started');
|
||||
});
|
||||
});
|
||||
};
|
||||
+3
-5
@@ -3,9 +3,8 @@ import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-
|
||||
import { join } from 'path';
|
||||
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { defineManifestTests } from '../app-dev/tests/manifest.tests';
|
||||
import { defineFrontComponentsTests } from '../app-dev/tests/front-components.tests';
|
||||
import { defineFunctionsTests } from '../app-dev/tests/functions.tests';
|
||||
import { defineManifestTests } from './tests/manifest.tests';
|
||||
import { defineEntitiesTests } from './tests/entities.tests';
|
||||
|
||||
const APP_PATH = join(__dirname, '../..');
|
||||
|
||||
@@ -20,6 +19,5 @@ describe('rich-app app:dev', () => {
|
||||
|
||||
defineConsoleOutputTests(() => result);
|
||||
defineManifestTests(APP_PATH);
|
||||
defineFunctionsTests(APP_PATH);
|
||||
defineFrontComponentsTests(APP_PATH);
|
||||
defineEntitiesTests(APP_PATH);
|
||||
});
|
||||
|
||||
+16
-18
@@ -4,6 +4,7 @@ import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
sources: {},
|
||||
yarnLock: '',
|
||||
application: {
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
@@ -21,7 +22,7 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
},
|
||||
frontComponents: [
|
||||
{
|
||||
builtComponentPath: 'front-components/src/root.front-component.mjs',
|
||||
builtComponentPath: 'src/root.front-component.mjs',
|
||||
builtComponentChecksum: '[checksum]',
|
||||
componentName: 'RootComponent',
|
||||
description: 'A root-level front component',
|
||||
@@ -30,8 +31,7 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
universalIdentifier: 'a0a1a2a3-a4a5-4000-8000-000000000001',
|
||||
},
|
||||
{
|
||||
builtComponentPath:
|
||||
'front-components/src/components/card.front-component.mjs',
|
||||
builtComponentPath: 'src/components/card.front-component.mjs',
|
||||
builtComponentChecksum: '[checksum]',
|
||||
componentName: 'CardDisplay',
|
||||
description: 'A component using an external component file',
|
||||
@@ -40,8 +40,7 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
universalIdentifier: 'i0i1i2i3-i4i5-4000-8000-000000000001',
|
||||
},
|
||||
{
|
||||
builtComponentPath:
|
||||
'front-components/src/components/greeting.front-component.mjs',
|
||||
builtComponentPath: 'src/components/greeting.front-component.mjs',
|
||||
builtComponentChecksum: '[checksum]',
|
||||
componentName: 'GreetingComponent',
|
||||
description: 'A component that uses greeting utility',
|
||||
@@ -50,8 +49,7 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
universalIdentifier: 'h0h1h2h3-h4h5-4000-8000-000000000001',
|
||||
},
|
||||
{
|
||||
builtComponentPath:
|
||||
'front-components/src/components/test.front-component.mjs',
|
||||
builtComponentPath: 'src/components/test.front-component.mjs',
|
||||
builtComponentChecksum: '[checksum]',
|
||||
componentName: 'TestComponent',
|
||||
description: 'A test front component',
|
||||
@@ -293,8 +291,8 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
functions: [
|
||||
{
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
builtHandlerPath: 'functions/src/root.function.mjs',
|
||||
handlerName: 'rootHandler',
|
||||
builtHandlerPath: 'src/root.function.mjs',
|
||||
handlerName: 'default.handler',
|
||||
name: 'root-function',
|
||||
sourceHandlerPath: 'src/root.function.ts',
|
||||
timeoutSeconds: 5,
|
||||
@@ -311,8 +309,8 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
},
|
||||
{
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
builtHandlerPath: 'functions/src/functions/greeting.function.mjs',
|
||||
handlerName: 'greetingHandler',
|
||||
builtHandlerPath: 'src/functions/greeting.function.mjs',
|
||||
handlerName: 'default.handler',
|
||||
name: 'greeting-function',
|
||||
sourceHandlerPath: 'src/functions/greeting.function.ts',
|
||||
timeoutSeconds: 5,
|
||||
@@ -322,17 +320,17 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
isAuthRequired: false,
|
||||
path: '/greet',
|
||||
type: 'route',
|
||||
universalIdentifier: 'g0g1g2g3-g4g5-4000-8000-000000000002',
|
||||
universalIdentifier: 'f7b12160-3d25-4c92-a13f-a325dd60eb04',
|
||||
},
|
||||
],
|
||||
universalIdentifier: 'g0g1g2g3-g4g5-4000-8000-000000000001',
|
||||
universalIdentifier: '9d412d9e-2caf-487c-8b66-d1585883dd4e',
|
||||
},
|
||||
{
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
builtHandlerPath: 'functions/src/functions/test-function-2.function.mjs',
|
||||
handlerName: 'testFunction2',
|
||||
builtHandlerPath: 'src/functions/test-function-2.function.mjs',
|
||||
handlerName: 'config.handler',
|
||||
name: 'test-function-2',
|
||||
sourceHandlerPath: 'src/utils/test-function-2.util.ts',
|
||||
sourceHandlerPath: 'src/functions/test-function-2.function.ts',
|
||||
timeoutSeconds: 2,
|
||||
triggers: [
|
||||
{
|
||||
@@ -345,8 +343,8 @@ export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
},
|
||||
{
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
builtHandlerPath: 'functions/src/functions/test-function.function.mjs',
|
||||
handlerName: 'handler',
|
||||
builtHandlerPath: 'src/functions/test-function.function.mjs',
|
||||
handlerName: 'default.handler',
|
||||
name: 'test-function',
|
||||
sourceHandlerPath: 'src/functions/test-function.function.ts',
|
||||
timeoutSeconds: 2,
|
||||
|
||||
-372
@@ -1,372 +0,0 @@
|
||||
{
|
||||
"application": {
|
||||
"applicationVariables": {
|
||||
"DEFAULT_RECIPIENT_NAME": {
|
||||
"description": "Default recipient name for postcards",
|
||||
"isSecret": false,
|
||||
"universalIdentifier": "19e94e59-d4fe-4251-8981-b96d0a9f74de",
|
||||
"value": "Alex Karp"
|
||||
}
|
||||
},
|
||||
"description": "A simple hello world app",
|
||||
"displayName": "Hello World",
|
||||
"functionRoleUniversalIdentifier": "b648f87b-1d26-4961-b974-0908fd991061",
|
||||
"icon": "IconWorld",
|
||||
"universalIdentifier": "4ec0391d-18d5-411c-b2f3-266ddc1c3ef7"
|
||||
},
|
||||
"frontComponents": [
|
||||
{
|
||||
"builtComponentPath": "front-components/src/root.front-component.mjs",
|
||||
"builtComponentChecksum": "[checksum]",
|
||||
"componentName": "RootComponent",
|
||||
"description": "A root-level front component",
|
||||
"name": "root-component",
|
||||
"sourceComponentPath": "src/root.front-component.tsx",
|
||||
"universalIdentifier": "a0a1a2a3-a4a5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"builtComponentPath": "front-components/src/components/card.front-component.mjs",
|
||||
"builtComponentChecksum": "[checksum]",
|
||||
"componentName": "CardDisplay",
|
||||
"description": "A component using an external component file",
|
||||
"name": "card-component",
|
||||
"sourceComponentPath": "src/components/card.front-component.tsx",
|
||||
"universalIdentifier": "i0i1i2i3-i4i5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"builtComponentPath": "front-components/src/components/greeting.front-component.mjs",
|
||||
"builtComponentChecksum": "[checksum]",
|
||||
"componentName": "GreetingComponent",
|
||||
"description": "A component that uses greeting utility",
|
||||
"name": "greeting-component",
|
||||
"sourceComponentPath": "src/components/greeting.front-component.tsx",
|
||||
"universalIdentifier": "h0h1h2h3-h4h5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"builtComponentPath": "front-components/src/components/test.front-component.mjs",
|
||||
"builtComponentChecksum": "[checksum]",
|
||||
"componentName": "TestComponent",
|
||||
"description": "A test front component",
|
||||
"name": "test-component",
|
||||
"sourceComponentPath": "src/components/test.front-component.tsx",
|
||||
"universalIdentifier": "f1234567-abcd-4000-8000-000000000001"
|
||||
}
|
||||
],
|
||||
"objectExtensions": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"description": "Priority level for the post card (1-10)",
|
||||
"label": "Priority",
|
||||
"name": "priority",
|
||||
"type": "NUMBER",
|
||||
"universalIdentifier": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
|
||||
},
|
||||
{
|
||||
"description": "Post card category",
|
||||
"label": "Category",
|
||||
"name": "category",
|
||||
"options": [
|
||||
{
|
||||
"color": "blue",
|
||||
"label": "Personal",
|
||||
"position": 0,
|
||||
"value": "PERSONAL"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"label": "Business",
|
||||
"position": 1,
|
||||
"value": "BUSINESS"
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"label": "Promotional",
|
||||
"position": 2,
|
||||
"value": "PROMOTIONAL"
|
||||
}
|
||||
],
|
||||
"type": "SELECT",
|
||||
"universalIdentifier": "8b9c0d1e-2f3a-4b5c-6d7e-8f9a0b1c2d3e"
|
||||
}
|
||||
],
|
||||
"targetObject": {
|
||||
"nameSingular": "postCard"
|
||||
}
|
||||
}
|
||||
],
|
||||
"objects": [
|
||||
{
|
||||
"description": "A simple root-level object",
|
||||
"fields": [
|
||||
{
|
||||
"label": "Title",
|
||||
"name": "title",
|
||||
"type": "TEXT",
|
||||
"universalIdentifier": "b0b1b2b3-b4b5-4000-8000-000000000002"
|
||||
},
|
||||
{
|
||||
"label": "Body",
|
||||
"name": "body",
|
||||
"type": "TEXT",
|
||||
"universalIdentifier": "b0b1b2b3-b4b5-4000-8000-000000000003"
|
||||
}
|
||||
],
|
||||
"icon": "IconNote",
|
||||
"labelPlural": "Root notes",
|
||||
"labelSingular": "Root note",
|
||||
"namePlural": "rootNotes",
|
||||
"nameSingular": "rootNote",
|
||||
"universalIdentifier": "b0b1b2b3-b4b5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"description": "A post card object",
|
||||
"fields": [
|
||||
{
|
||||
"description": "Postcard's content",
|
||||
"icon": "IconAbc",
|
||||
"label": "Content",
|
||||
"name": "content",
|
||||
"type": "TEXT",
|
||||
"universalIdentifier": "58a0a314-d7ea-4865-9850-7fb84e72f30b"
|
||||
},
|
||||
{
|
||||
"icon": "IconUser",
|
||||
"label": "Recipient name",
|
||||
"name": "recipientName",
|
||||
"type": "FULL_NAME",
|
||||
"universalIdentifier": "c6aa31f3-da76-4ac6-889f-475e226009ac"
|
||||
},
|
||||
{
|
||||
"icon": "IconHome",
|
||||
"label": "Recipient address",
|
||||
"name": "recipientAddress",
|
||||
"type": "ADDRESS",
|
||||
"universalIdentifier": "95045777-a0ad-49ec-98f9-22f9fc0c8266"
|
||||
},
|
||||
{
|
||||
"defaultValue": "'DRAFT'",
|
||||
"icon": "IconSend",
|
||||
"label": "Status",
|
||||
"name": "status",
|
||||
"options": [
|
||||
{
|
||||
"color": "gray",
|
||||
"label": "Draft",
|
||||
"position": 0,
|
||||
"value": "DRAFT"
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"label": "Sent",
|
||||
"position": 1,
|
||||
"value": "SENT"
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"label": "Delivered",
|
||||
"position": 2,
|
||||
"value": "DELIVERED"
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"label": "Returned",
|
||||
"position": 3,
|
||||
"value": "RETURNED"
|
||||
}
|
||||
],
|
||||
"type": "SELECT",
|
||||
"universalIdentifier": "87b675b8-dd8c-4448-b4ca-20e5a2234a1e"
|
||||
},
|
||||
{
|
||||
"defaultValue": null,
|
||||
"icon": "IconCheck",
|
||||
"isNullable": true,
|
||||
"label": "Delivered at",
|
||||
"name": "deliveredAt",
|
||||
"type": "DATE_TIME",
|
||||
"universalIdentifier": "e06abe72-5b44-4e7f-93be-afc185a3c433"
|
||||
}
|
||||
],
|
||||
"icon": "IconMail",
|
||||
"labelPlural": "Post cards",
|
||||
"labelSingular": "Post card",
|
||||
"namePlural": "postCards",
|
||||
"nameSingular": "postCard",
|
||||
"universalIdentifier": "54b589ca-eeed-4950-a176-358418b85c05"
|
||||
}
|
||||
],
|
||||
"packageJson": {
|
||||
"name": "rich-app",
|
||||
"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",
|
||||
"app:build": "twenty app:build",
|
||||
"app:sync": "twenty app:sync",
|
||||
"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": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^19.0.2",
|
||||
"react": "^19.0.2",
|
||||
"eslint": "^9.32.0",
|
||||
"typescript-eslint": "^8.50.0"
|
||||
}
|
||||
},
|
||||
"roles": [
|
||||
{
|
||||
"canBeAssignedToAgents": false,
|
||||
"canBeAssignedToApiKeys": false,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canReadAllObjectRecords": true,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"description": "A simple root-level role",
|
||||
"label": "Root role",
|
||||
"universalIdentifier": "c0c1c2c3-c4c5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"canBeAssignedToAgents": false,
|
||||
"canBeAssignedToApiKeys": false,
|
||||
"canBeAssignedToUsers": false,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canReadAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"description": "Default role for function Twenty client",
|
||||
"fieldPermissions": [
|
||||
{
|
||||
"canReadFieldValue": false,
|
||||
"canUpdateFieldValue": false,
|
||||
"fieldName": "content",
|
||||
"objectNameSingular": "postCard"
|
||||
}
|
||||
],
|
||||
"label": "Default function role",
|
||||
"objectPermissions": [
|
||||
{
|
||||
"canDestroyObjectRecords": false,
|
||||
"canReadObjectRecords": true,
|
||||
"canSoftDeleteObjectRecords": false,
|
||||
"canUpdateObjectRecords": true,
|
||||
"objectNameSingular": "postCard"
|
||||
}
|
||||
],
|
||||
"permissionFlags": [
|
||||
"APPLICATIONS"
|
||||
],
|
||||
"universalIdentifier": "b648f87b-1d26-4961-b974-0908fd991061"
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"builtHandlerChecksum": "[checksum]",
|
||||
"builtHandlerPath": "functions/src/root.function.mjs",
|
||||
"handlerName": "rootHandler",
|
||||
"name": "root-function",
|
||||
"sourceHandlerPath": "src/root.function.ts",
|
||||
"timeoutSeconds": 5,
|
||||
"triggers": [
|
||||
{
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false,
|
||||
"path": "/root",
|
||||
"type": "route",
|
||||
"universalIdentifier": "f0f1f2f3-f4f5-4000-8000-000000000002"
|
||||
}
|
||||
],
|
||||
"universalIdentifier": "f0f1f2f3-f4f5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"builtHandlerChecksum": "[checksum]",
|
||||
"builtHandlerPath": "functions/src/functions/greeting.function.mjs",
|
||||
"handlerName": "greetingHandler",
|
||||
"name": "greeting-function",
|
||||
"sourceHandlerPath": "src/functions/greeting.function.ts",
|
||||
"timeoutSeconds": 5,
|
||||
"triggers": [
|
||||
{
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false,
|
||||
"path": "/greet",
|
||||
"type": "route",
|
||||
"universalIdentifier": "g0g1g2g3-g4g5-4000-8000-000000000002"
|
||||
}
|
||||
],
|
||||
"universalIdentifier": "g0g1g2g3-g4g5-4000-8000-000000000001"
|
||||
},
|
||||
{
|
||||
"builtHandlerChecksum": "[checksum]",
|
||||
"builtHandlerPath": "functions/src/functions/test-function-2.function.mjs",
|
||||
"handlerName": "testFunction2",
|
||||
"name": "test-function-2",
|
||||
"sourceHandlerPath": "src/utils/test-function-2.util.ts",
|
||||
"timeoutSeconds": 2,
|
||||
"triggers": [
|
||||
{
|
||||
"pattern": "0 0 1 1 *",
|
||||
"type": "cron",
|
||||
"universalIdentifier": "9fd0dda9-4664-4fbc-9656-509f4477b9ff"
|
||||
}
|
||||
],
|
||||
"universalIdentifier": "eb3ffc98-88ec-45d4-9b4a-56833b219ccb"
|
||||
},
|
||||
{
|
||||
"builtHandlerChecksum": "[checksum]",
|
||||
"builtHandlerPath": "functions/src/functions/test-function.function.mjs",
|
||||
"handlerName": "handler",
|
||||
"name": "test-function",
|
||||
"sourceHandlerPath": "src/functions/test-function.function.ts",
|
||||
"timeoutSeconds": 2,
|
||||
"triggers": [
|
||||
{
|
||||
"forwardedRequestHeaders": [
|
||||
"signature"
|
||||
],
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false,
|
||||
"path": "/post-card/create",
|
||||
"type": "route",
|
||||
"universalIdentifier": "c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6"
|
||||
},
|
||||
{
|
||||
"pattern": "0 0 1 1 *",
|
||||
"type": "cron",
|
||||
"universalIdentifier": "dd802808-0695-49e1-98c9-d5c9e2704ce2"
|
||||
},
|
||||
{
|
||||
"eventName": "person.created",
|
||||
"type": "databaseEvent",
|
||||
"universalIdentifier": "203f1df3-4a82-4d06-a001-b8cf22a31156"
|
||||
}
|
||||
],
|
||||
"universalIdentifier": "e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf"
|
||||
}
|
||||
]
|
||||
}
|
||||
+10
-21
@@ -14,34 +14,23 @@ export const defineConsoleOutputTests = (
|
||||
expect(output).toContain('[init] 📁 App Path:');
|
||||
});
|
||||
|
||||
it('should contain manifest-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'manifest-watch');
|
||||
it('should contain dev-mode build messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'dev-mode');
|
||||
|
||||
expect(output).toContain('[manifest-watch] 🔄 Building...');
|
||||
expect(output).toContain('[manifest-watch] ✓ Loaded "Hello World"');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 2 object(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 4 function(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 4 front component(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 2 role(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Written to');
|
||||
expect(output).toContain('[manifest-watch] 📂 Watcher started');
|
||||
expect(output).toContain('[dev-mode] Building manifest...');
|
||||
expect(output).toContain('[dev-mode] Successfully built manifest');
|
||||
});
|
||||
|
||||
it('should contain functions-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'functions-watch');
|
||||
it('should contain dev-mode function build messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'dev-mode');
|
||||
|
||||
expect(output).toContain('[functions-watch] 📦 Building...');
|
||||
expect(output).toContain('[functions-watch] ✓ Built');
|
||||
expect(output).toContain('[dev-mode] ✓ Successfully built');
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
it('should contain dev-mode sync messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'dev-mode');
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
expect(output).toContain('[dev-mode] ✓ Synced');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+19
-6
@@ -1,15 +1,24 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
|
||||
export const defineFunctionsTests = (appPath: string): void => {
|
||||
export const defineEntitiesTests = (appPath: string): void => {
|
||||
const outputDir = join(appPath, OUTPUT_DIR);
|
||||
describe('functions', () => {
|
||||
it('should have built functions preserving source path structure', async () => {
|
||||
const functionsDir = join(appPath, '.twenty/output/functions');
|
||||
const files = await fs.readdir(functionsDir, { recursive: true });
|
||||
const files = await fs.readdir(outputDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
|
||||
expect(sortedFiles).toEqual([
|
||||
'manifest.json',
|
||||
'src',
|
||||
'src/components',
|
||||
'src/components/card.front-component.mjs',
|
||||
'src/components/card.front-component.mjs.map',
|
||||
'src/components/greeting.front-component.mjs',
|
||||
'src/components/greeting.front-component.mjs.map',
|
||||
'src/components/test.front-component.mjs',
|
||||
'src/components/test.front-component.mjs.map',
|
||||
'src/functions',
|
||||
'src/functions/greeting.function.mjs',
|
||||
'src/functions/greeting.function.mjs.map',
|
||||
@@ -17,19 +26,23 @@ export const defineFunctionsTests = (appPath: string): void => {
|
||||
'src/functions/test-function-2.function.mjs.map',
|
||||
'src/functions/test-function.function.mjs',
|
||||
'src/functions/test-function.function.mjs.map',
|
||||
'src/root.front-component.mjs',
|
||||
'src/root.front-component.mjs.map',
|
||||
'src/root.function.mjs',
|
||||
'src/root.function.mjs.map',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not create shared chunk files for utilities', async () => {
|
||||
const functionsDir = join(appPath, '.twenty/output/functions');
|
||||
const files = await fs.readdir(functionsDir, { recursive: true });
|
||||
const files = await fs.readdir(outputDir, { recursive: true });
|
||||
|
||||
// Chunk files have a hash suffix like "greeting.util-CipJsYK0.mjs"
|
||||
const chunkFiles = files
|
||||
.map((f) => f.toString())
|
||||
.filter((f) => f.endsWith('.mjs') && !f.includes('.function.mjs'));
|
||||
.filter((f) => f.endsWith('.mjs') && !f.includes('.function.mjs'))
|
||||
.filter(
|
||||
(f) => f.endsWith('.mjs') && !f.includes('.front-component.mjs'),
|
||||
);
|
||||
|
||||
expect(chunkFiles).toEqual([]);
|
||||
});
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
|
||||
export const defineFrontComponentsTests = (appPath: string): void => {
|
||||
describe('front-components', () => {
|
||||
it('should have built front components preserving source path structure', async () => {
|
||||
const frontComponentsDir = join(
|
||||
appPath,
|
||||
'.twenty/output/front-components',
|
||||
);
|
||||
const files = await fs.readdir(frontComponentsDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
|
||||
expect(sortedFiles).toEqual([
|
||||
'src',
|
||||
'src/components',
|
||||
'src/components/card.front-component.mjs',
|
||||
'src/components/card.front-component.mjs.map',
|
||||
'src/components/greeting.front-component.mjs',
|
||||
'src/components/greeting.front-component.mjs.map',
|
||||
'src/components/test.front-component.mjs',
|
||||
'src/components/test.front-component.mjs.map',
|
||||
'src/root.front-component.mjs',
|
||||
'src/root.front-component.mjs.map',
|
||||
]);
|
||||
});
|
||||
});
|
||||
};
|
||||
+2
-2
@@ -6,13 +6,13 @@ const greetingHandler = () => {
|
||||
};
|
||||
|
||||
export default defineFunction({
|
||||
universalIdentifier: 'g0g1g2g3-g4g5-4000-8000-000000000001',
|
||||
universalIdentifier: '9d412d9e-2caf-487c-8b66-d1585883dd4e',
|
||||
name: 'greeting-function',
|
||||
timeoutSeconds: 5,
|
||||
handler: greetingHandler,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'g0g1g2g3-g4g5-4000-8000-000000000002',
|
||||
universalIdentifier: 'f7b12160-3d25-4c92-a13f-a325dd60eb04',
|
||||
type: 'route',
|
||||
path: '/greet',
|
||||
httpMethod: 'GET',
|
||||
|
||||
+1
-5
@@ -1,7 +1,3 @@
|
||||
export const testFunction2 = () => {
|
||||
const Twenty = require('../../generated').default;
|
||||
|
||||
const client = new Twenty();
|
||||
|
||||
return client.query('testQuery');
|
||||
return 'test-function-2-result';
|
||||
};
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { runAppBuild } from '@/cli/__tests__/integration/utils/run-app-build.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { defineFrontComponentsTests } from '../app-dev/tests/front-components.tests';
|
||||
import { defineFunctionsTests } from '../app-dev/tests/functions.tests';
|
||||
import { defineManifestTests } from '../app-dev/tests/manifest.tests';
|
||||
|
||||
const APP_PATH = join(__dirname, '../..');
|
||||
|
||||
describe('root-app app:build', () => {
|
||||
let result: RunCliCommandResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runAppBuild({ appPath: APP_PATH });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
}, 60000);
|
||||
|
||||
defineConsoleOutputTests(() => result);
|
||||
defineManifestTests(APP_PATH);
|
||||
defineFunctionsTests(APP_PATH);
|
||||
defineFrontComponentsTests(APP_PATH);
|
||||
});
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
import { getOutputByPrefix } from '@/cli/__tests__/integration/utils/get-output-by-prefix.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
|
||||
export const defineConsoleOutputTests = (
|
||||
getResult: () => RunCliCommandResult,
|
||||
): void => {
|
||||
describe('console output', () => {
|
||||
it('should contain init messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'init');
|
||||
|
||||
expect(output).toContain('[init] 🚀 Building Twenty Application');
|
||||
expect(output).toContain('[init] 📁 App Path:');
|
||||
expect(output).toContain('[init] ✅ Build completed successfully');
|
||||
});
|
||||
|
||||
it('should contain manifest-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'manifest-watch');
|
||||
|
||||
expect(output).toContain('[manifest-watch] 🔄 Building...');
|
||||
expect(output).toContain('[manifest-watch] ✓ Loaded "Root App"');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 object(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 function(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 front component(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 role(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Written to');
|
||||
});
|
||||
|
||||
it('should contain functions-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'functions-watch');
|
||||
|
||||
expect(output).toContain('[functions-watch] 📦 Building...');
|
||||
expect(output).toContain('[functions-watch] ✓ Built');
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
});
|
||||
|
||||
it('should not contain watching messages', () => {
|
||||
const output = getResult().output;
|
||||
|
||||
expect(output).not.toContain('👀 Watching for changes...');
|
||||
expect(output).not.toContain('📂 Watcher started');
|
||||
});
|
||||
});
|
||||
};
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import { FieldType } from '@/application';
|
||||
import type { ApplicationManifest } from 'twenty-shared/application';
|
||||
|
||||
export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
sources: {},
|
||||
yarnLock: '',
|
||||
packageJson: {
|
||||
name: 'root-app',
|
||||
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',
|
||||
'app:build': 'twenty app:build',
|
||||
'app:sync': 'twenty app:sync',
|
||||
'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': 'latest',
|
||||
},
|
||||
devDependencies: {
|
||||
typescript: '^5.9.3',
|
||||
'@types/node': '^24.7.2',
|
||||
'@types/react': '^19.0.2',
|
||||
react: '^19.0.2',
|
||||
eslint: '^9.32.0',
|
||||
'typescript-eslint': '^8.50.0',
|
||||
},
|
||||
},
|
||||
application: {
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000001',
|
||||
displayName: 'Root App',
|
||||
description: 'An app with all entities at root level',
|
||||
icon: 'IconFolder',
|
||||
functionRoleUniversalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000002',
|
||||
},
|
||||
objects: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
nameSingular: 'myNote',
|
||||
namePlural: 'myNotes',
|
||||
labelSingular: 'My note',
|
||||
labelPlural: 'My notes',
|
||||
description: 'A simple root-level object',
|
||||
icon: 'IconNote',
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000031',
|
||||
type: FieldType.TEXT,
|
||||
label: 'Title',
|
||||
name: 'title',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
functions: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000010',
|
||||
name: 'my-function',
|
||||
timeoutSeconds: 5,
|
||||
triggers: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000011',
|
||||
type: 'route',
|
||||
path: '/my-function',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
],
|
||||
handlerName: 'default.handler',
|
||||
sourceHandlerPath: 'my.function.ts',
|
||||
builtHandlerPath: 'my.function.mjs',
|
||||
builtHandlerChecksum: '[checksum]',
|
||||
},
|
||||
],
|
||||
frontComponents: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000020',
|
||||
name: 'my-component',
|
||||
description: 'A root-level front component',
|
||||
componentName: 'MyComponent',
|
||||
sourceComponentPath: 'my.front-component.tsx',
|
||||
builtComponentPath: 'my.front-component.mjs',
|
||||
builtComponentChecksum: '[checksum]',
|
||||
},
|
||||
],
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000040',
|
||||
label: 'My role',
|
||||
description: 'A simple root-level role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canUpdateAllSettings: false,
|
||||
canBeAssignedToAgents: false,
|
||||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToApiKeys: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"application": {
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000001",
|
||||
"displayName": "Root App",
|
||||
"description": "An app with all entities at root level",
|
||||
"icon": "IconFolder",
|
||||
"functionRoleUniversalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000002"
|
||||
},
|
||||
"objects": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000030",
|
||||
"nameSingular": "myNote",
|
||||
"namePlural": "myNotes",
|
||||
"labelSingular": "My note",
|
||||
"labelPlural": "My notes",
|
||||
"description": "A simple root-level object",
|
||||
"icon": "IconNote",
|
||||
"fields": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000031",
|
||||
"type": "TEXT",
|
||||
"label": "Title",
|
||||
"name": "title"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000010",
|
||||
"name": "my-function",
|
||||
"timeoutSeconds": 5,
|
||||
"triggers": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000011",
|
||||
"type": "route",
|
||||
"path": "/my-function",
|
||||
"httpMethod": "GET",
|
||||
"isAuthRequired": false
|
||||
}
|
||||
],
|
||||
"handlerName": "myHandler",
|
||||
"sourceHandlerPath": "my.function.ts",
|
||||
"builtHandlerPath": "functions/my.function.mjs",
|
||||
"builtHandlerChecksum": "[checksum]"
|
||||
}
|
||||
],
|
||||
"frontComponents": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000020",
|
||||
"name": "my-component",
|
||||
"description": "A root-level front component",
|
||||
"componentName": "MyComponent",
|
||||
"sourceComponentPath": "my.front-component.tsx",
|
||||
"builtComponentPath": "front-components/my.front-component.mjs",
|
||||
"builtComponentChecksum": "[checksum]"
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000040",
|
||||
"label": "My role",
|
||||
"description": "A simple root-level role",
|
||||
"canReadAllObjectRecords": true,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"canBeAssignedToAgents": false,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canBeAssignedToApiKeys": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+10
-21
@@ -14,34 +14,23 @@ export const defineConsoleOutputTests = (
|
||||
expect(output).toContain('[init] 📁 App Path:');
|
||||
});
|
||||
|
||||
it('should contain manifest-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'manifest-watch');
|
||||
it('should contain dev-mode build messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'dev-mode');
|
||||
|
||||
expect(output).toContain('[manifest-watch] 🔄 Building...');
|
||||
expect(output).toContain('[manifest-watch] ✓ Loaded "Root App"');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 object(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 function(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 front component(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Found 1 role(s)');
|
||||
expect(output).toContain('[manifest-watch] ✓ Written to');
|
||||
expect(output).toContain('[manifest-watch] 📂 Watcher started');
|
||||
expect(output).toContain('[dev-mode] Building manifest...');
|
||||
expect(output).toContain('[dev-mode] Successfully built manifest');
|
||||
});
|
||||
|
||||
it('should contain functions-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'functions-watch');
|
||||
it('should contain dev-mode function build messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'dev-mode');
|
||||
|
||||
expect(output).toContain('[functions-watch] 📦 Building...');
|
||||
expect(output).toContain('[functions-watch] ✓ Built');
|
||||
expect(output).toContain('[dev-mode] ✓ Successfully built');
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
it('should contain dev-mode sync messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'dev-mode');
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
expect(output).toContain('[dev-mode] ✓ Synced');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+7
-7
@@ -4,14 +4,14 @@ import { join } from 'path';
|
||||
export const defineFrontComponentsTests = (appPath: string): void => {
|
||||
describe('front-components', () => {
|
||||
it('should have built front components at root level', async () => {
|
||||
const frontComponentsDir = join(
|
||||
appPath,
|
||||
'.twenty/output/front-components',
|
||||
);
|
||||
const files = await fs.readdir(frontComponentsDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
const outputDir = join(appPath, '.twenty/output');
|
||||
const files = await fs.readdir(outputDir, { recursive: true });
|
||||
const componentFiles = files
|
||||
.map((f) => f.toString())
|
||||
.filter((f) => f.includes('.front-component.'))
|
||||
.sort();
|
||||
|
||||
expect(sortedFiles).toEqual([
|
||||
expect(componentFiles).toEqual([
|
||||
'my.front-component.mjs',
|
||||
'my.front-component.mjs.map',
|
||||
]);
|
||||
|
||||
+7
-4
@@ -4,11 +4,14 @@ import { join } from 'path';
|
||||
export const defineFunctionsTests = (appPath: string): void => {
|
||||
describe('functions', () => {
|
||||
it('should have built functions at root level', async () => {
|
||||
const functionsDir = join(appPath, '.twenty/output/functions');
|
||||
const files = await fs.readdir(functionsDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
const outputDir = join(appPath, '.twenty/output');
|
||||
const files = await fs.readdir(outputDir, { recursive: true });
|
||||
const functionFiles = files
|
||||
.map((f) => f.toString())
|
||||
.filter((f) => f.includes('.function.'))
|
||||
.sort();
|
||||
|
||||
expect(sortedFiles).toEqual(['my.function.mjs', 'my.function.mjs.map']);
|
||||
expect(functionFiles).toEqual(['my.function.mjs', 'my.function.mjs.map']);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+8
-11
@@ -3,6 +3,7 @@ import { join } from 'path';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
|
||||
import { normalizeManifestForComparison } from '@/cli/__tests__/integration/utils/normalize-manifest.util';
|
||||
import { EXPECTED_MANIFEST } from '@/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest';
|
||||
|
||||
export const defineManifestTests = (appPath: string): void => {
|
||||
describe('manifest', () => {
|
||||
@@ -16,21 +17,17 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
it('should have correct manifest content', async () => {
|
||||
const manifestPath = join(appPath, '.twenty/output/manifest.json');
|
||||
const manifest: ApplicationManifest = await fs.readJSON(manifestPath);
|
||||
const expectedPath = join(
|
||||
appPath,
|
||||
'__integration__/app-dev/manifest.expected.json',
|
||||
);
|
||||
const expected: ApplicationManifest = await fs.readJSON(expectedPath);
|
||||
|
||||
expect(manifest.application).toEqual(expected.application);
|
||||
expect(manifest.objects).toEqual(expected.objects);
|
||||
expect(manifest.application).toEqual(EXPECTED_MANIFEST.application);
|
||||
expect(manifest.objects).toEqual(EXPECTED_MANIFEST.objects);
|
||||
|
||||
expect(
|
||||
normalizeManifestForComparison({ functions: manifest.functions })
|
||||
.functions,
|
||||
).toEqual(
|
||||
normalizeManifestForComparison({ functions: expected.functions })
|
||||
.functions,
|
||||
normalizeManifestForComparison({
|
||||
functions: EXPECTED_MANIFEST.functions,
|
||||
}).functions,
|
||||
);
|
||||
|
||||
for (const fn of manifest.functions) {
|
||||
@@ -45,7 +42,7 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
}).frontComponents,
|
||||
).toEqual(
|
||||
normalizeManifestForComparison({
|
||||
frontComponents: expected.frontComponents,
|
||||
frontComponents: EXPECTED_MANIFEST.frontComponents,
|
||||
}).frontComponents,
|
||||
);
|
||||
|
||||
@@ -54,7 +51,7 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
expect(component.builtComponentChecksum).not.toBeNull();
|
||||
expect(typeof component.builtComponentChecksum).toBe('string');
|
||||
}
|
||||
expect(manifest.roles).toEqual(expected.roles);
|
||||
expect(manifest.roles).toEqual(EXPECTED_MANIFEST.roles);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type LogPrefix =
|
||||
| 'init'
|
||||
| 'dev-mode'
|
||||
| 'manifest-watch'
|
||||
| 'functions-watch'
|
||||
| 'front-components-watch';
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import {
|
||||
runCliCommand,
|
||||
type RunCliCommandResult,
|
||||
} from './run-cli-command.util';
|
||||
|
||||
export type RunAppBuildOptions = {
|
||||
appPath: string;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export const runAppBuild = (
|
||||
options: RunAppBuildOptions,
|
||||
): Promise<RunCliCommandResult> => {
|
||||
const { appPath, timeout = 30000 } = options;
|
||||
|
||||
// app:build runs once and exits, so we don't wait for specific output
|
||||
return runCliCommand({
|
||||
command: 'app:build',
|
||||
args: [appPath],
|
||||
timeout,
|
||||
});
|
||||
};
|
||||
@@ -16,11 +16,7 @@ export const runAppDev = (
|
||||
return runCliCommand({
|
||||
command: 'app:dev',
|
||||
args: [appPath],
|
||||
waitForOutput: [
|
||||
'[manifest-watch] ✓ Written to',
|
||||
'[functions-watch] ✓ Built',
|
||||
'[front-components-watch] ✓ Built',
|
||||
],
|
||||
waitForOutput: ['[dev-mode] ✓ Synced'],
|
||||
timeout,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -33,7 +33,11 @@ export const runCliCommand = (
|
||||
{
|
||||
cwd: CLI_DIR,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: { ...process.env, FORCE_COLOR: '0' },
|
||||
env: {
|
||||
...process.env,
|
||||
FORCE_COLOR: '0',
|
||||
TWENTY_SKIP_SERVER_CHECK: 'true',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user