Sync built files (#17379)
as title, upload built files to local storage --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+1
-2
@@ -10,9 +10,8 @@ import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getR
|
||||
import { getRefName } from '@/object-record/cache/utils/getRefName';
|
||||
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, pascalCase } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
import { pascalCase } from '~/utils/string/pascalCase';
|
||||
|
||||
export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
||||
objectMetadataItems,
|
||||
|
||||
+1
-2
@@ -4,8 +4,7 @@ import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { isFieldValueEmpty } from '@/object-record/record-field/ui/utils/isFieldValueEmpty';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { pascalCase } from '~/utils/string/pascalCase';
|
||||
import { isDefined, pascalCase } from 'twenty-shared/utils';
|
||||
|
||||
type UsePreviewRecordParams = {
|
||||
objectNameSingular: string;
|
||||
|
||||
@@ -5,6 +5,12 @@ export default [
|
||||
{
|
||||
ignores: ['**/dist/**'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
rules: {
|
||||
'prettier/prettier': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineApp } from '../define-app';
|
||||
import { defineApp } from '@/application';
|
||||
|
||||
describe('defineApp', () => {
|
||||
it('should return the config when valid', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineFrontComponent } from '../front-components/define-front-component';
|
||||
import { defineFrontComponent } from '@/application';
|
||||
|
||||
// Mock component for testing
|
||||
const MockComponent = () => null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineFunction } from '../functions/define-function';
|
||||
import { defineFunction } from '@/application';
|
||||
|
||||
// Mock handler for testing
|
||||
const mockHandler = async () => ({ success: true });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineObject } from '../objects/define-object';
|
||||
import { defineObject } from '@/application';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { type ObjectManifest } from 'twenty-shared/application';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineRole } from '../roles/define-role';
|
||||
import { defineRole } from '@/application';
|
||||
|
||||
describe('defineRole', () => {
|
||||
const validConfig = {
|
||||
|
||||
@@ -6,6 +6,5 @@ export type {
|
||||
FullNameMetadata as FullNameField,
|
||||
LinksMetadata as LinksField,
|
||||
PhonesMetadata as PhonesField,
|
||||
RichTextV2Metadata as RichTextField
|
||||
RichTextV2Metadata as RichTextField,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FrontComponentConfig } from './front-component-config';
|
||||
import { type FrontComponentConfig } from '@/application/front-components/front-component-config';
|
||||
|
||||
/**
|
||||
* Define a front component configuration with validation.
|
||||
|
||||
@@ -4,7 +4,10 @@ export type FrontComponentType = React.ComponentType<any>;
|
||||
|
||||
export type FrontComponentConfig = Omit<
|
||||
FrontComponentManifest,
|
||||
'sourceComponentPath' | 'builtComponentPath' | 'builtComponentChecksum' | 'componentName'
|
||||
| 'sourceComponentPath'
|
||||
| 'builtComponentPath'
|
||||
| 'builtComponentChecksum'
|
||||
| 'componentName'
|
||||
> & {
|
||||
name?: string;
|
||||
description?: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FunctionConfig } from './function-config';
|
||||
import { type FunctionConfig } from '@/application/functions/function-config';
|
||||
|
||||
/**
|
||||
* Define a serverless function configuration with validation.
|
||||
@@ -68,7 +68,9 @@ export const defineFunction = <T extends FunctionConfig>(config: T): T => {
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown trigger type: ${(trigger as { type: string }).type}`);
|
||||
throw new Error(
|
||||
`Unknown trigger type: ${(trigger as { type: string }).type}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ export type FunctionHandler = (...args: any[]) => any | Promise<any>;
|
||||
|
||||
export type FunctionConfig = Omit<
|
||||
ServerlessFunctionManifest,
|
||||
'sourceHandlerPath' | 'builtHandlerPath' | 'builtHandlerChecksum' | 'handlerName'
|
||||
| 'sourceHandlerPath'
|
||||
| 'builtHandlerPath'
|
||||
| 'builtHandlerChecksum'
|
||||
| 'handlerName'
|
||||
> & {
|
||||
name?: string;
|
||||
description?: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type RoleConfig } from '../role-config';
|
||||
import { type RoleConfig } from '@/application/role-config';
|
||||
|
||||
/**
|
||||
* Define a role configuration with validation.
|
||||
@@ -36,7 +36,10 @@ export const defineRole = <T extends RoleConfig>(config: T): T => {
|
||||
// Validate object permissions if provided
|
||||
if (config.objectPermissions) {
|
||||
for (const permission of config.objectPermissions) {
|
||||
if (!permission.objectNameSingular && !permission.objectUniversalIdentifier) {
|
||||
if (
|
||||
!permission.objectNameSingular &&
|
||||
!permission.objectUniversalIdentifier
|
||||
) {
|
||||
throw new Error(
|
||||
'Object permission must have either objectNameSingular or objectUniversalIdentifier',
|
||||
);
|
||||
@@ -47,7 +50,10 @@ export const defineRole = <T extends RoleConfig>(config: T): T => {
|
||||
// Validate field permissions if provided
|
||||
if (config.fieldPermissions) {
|
||||
for (const permission of config.fieldPermissions) {
|
||||
if (!permission.objectNameSingular && !permission.objectUniversalIdentifier) {
|
||||
if (
|
||||
!permission.objectNameSingular &&
|
||||
!permission.objectUniversalIdentifier
|
||||
) {
|
||||
throw new Error(
|
||||
'Field permission must have either objectNameSingular or objectUniversalIdentifier',
|
||||
);
|
||||
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
import { runAppBuild } from '@/cli/__tests__/integration/utils/run-app-build.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
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';
|
||||
import { defineManifestTests } from '../app-dev/tests/manifest.tests';
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
|
||||
const APP_PATH = join(__dirname, '../..');
|
||||
|
||||
|
||||
+4
-1
@@ -33,7 +33,10 @@ export const defineConsoleOutputTests = (
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'front-components-watch');
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
|
||||
+3
-3
@@ -3,9 +3,9 @@ import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-
|
||||
import { join } from 'path';
|
||||
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { defineFrontComponentsTests } from './tests/front-components.tests';
|
||||
import { defineFunctionsTests } from './tests/functions.tests';
|
||||
import { defineManifestTests } from './tests/manifest.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, '../..');
|
||||
|
||||
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
import { FieldType } from '@/application';
|
||||
import type { ApplicationManifest } from 'twenty-shared/application';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
export const EXPECTED_MANIFEST: ApplicationManifest = {
|
||||
sources: {},
|
||||
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: FieldType.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: FieldType.SELECT,
|
||||
universalIdentifier: '8b9c0d1e-2f3a-4b5c-6d7e-8f9a0b1c2d3e',
|
||||
},
|
||||
],
|
||||
targetObject: {
|
||||
nameSingular: 'postCard',
|
||||
},
|
||||
},
|
||||
],
|
||||
objects: [
|
||||
{
|
||||
description: 'A simple root-level object',
|
||||
fields: [
|
||||
{
|
||||
label: 'Title',
|
||||
name: 'title',
|
||||
type: FieldType.TEXT,
|
||||
universalIdentifier: 'b0b1b2b3-b4b5-4000-8000-000000000002',
|
||||
},
|
||||
{
|
||||
label: 'Body',
|
||||
name: 'body',
|
||||
type: FieldType.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: FieldType.TEXT,
|
||||
universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b',
|
||||
},
|
||||
{
|
||||
icon: 'IconUser',
|
||||
label: 'Recipient name',
|
||||
name: 'recipientName',
|
||||
type: FieldType.FULL_NAME,
|
||||
universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac',
|
||||
},
|
||||
{
|
||||
icon: 'IconHome',
|
||||
label: 'Recipient address',
|
||||
name: 'recipientAddress',
|
||||
type: FieldType.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: FieldType.SELECT,
|
||||
universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
icon: 'IconCheck',
|
||||
isNullable: true,
|
||||
label: 'Delivered at',
|
||||
name: 'deliveredAt',
|
||||
type: FieldType.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: [PermissionFlagType.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',
|
||||
},
|
||||
],
|
||||
};
|
||||
+7
-2
@@ -8,7 +8,9 @@ export const defineConsoleOutputTests = (
|
||||
it('should contain init messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'init');
|
||||
|
||||
expect(output).toContain('[init] 🚀 Starting Twenty Application Development Mode');
|
||||
expect(output).toContain(
|
||||
'[init] 🚀 Starting Twenty Application Development Mode',
|
||||
);
|
||||
expect(output).toContain('[init] 📁 App Path:');
|
||||
});
|
||||
|
||||
@@ -33,7 +35,10 @@ export const defineConsoleOutputTests = (
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'front-components-watch');
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
|
||||
+4
-1
@@ -4,7 +4,10 @@ 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 frontComponentsDir = join(
|
||||
appPath,
|
||||
'.twenty/output/front-components',
|
||||
);
|
||||
const files = await fs.readdir(frontComponentsDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
|
||||
|
||||
+5
-3
@@ -2,7 +2,7 @@ import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
|
||||
import { normalizeManifestForComparison } from '@/cli/__tests__/integration/utils/normalize-manifest.util';
|
||||
import expectedManifest from '../manifest.expected.json';
|
||||
import { EXPECTED_MANIFEST } from '../expected-manifest';
|
||||
|
||||
export const defineManifestTests = (appPath: string): void => {
|
||||
const manifestOutputPath = join(appPath, '.twenty/output/manifest.json');
|
||||
@@ -16,7 +16,7 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
const { sources: _sources, ...sanitizedManifest } = manifest;
|
||||
|
||||
expect(normalizeManifestForComparison(sanitizedManifest)).toEqual(
|
||||
normalizeManifestForComparison(expectedManifest),
|
||||
normalizeManifestForComparison(EXPECTED_MANIFEST),
|
||||
);
|
||||
|
||||
for (const fn of manifest.functions) {
|
||||
@@ -36,7 +36,9 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
const manifest = await fs.readJson(manifestOutputPath);
|
||||
|
||||
expect(manifest?.application.displayName).toBe('Hello World');
|
||||
expect(manifest?.application.description).toBe('A simple hello world app');
|
||||
expect(manifest?.application.description).toBe(
|
||||
'A simple hello world app',
|
||||
);
|
||||
});
|
||||
|
||||
it('should load all entity types', async () => {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { runAppBuild } from '../../../../integration/utils/run-app-build.util';
|
||||
import { type RunCliCommandResult } from '../../../../integration/utils/run-cli-command.util';
|
||||
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';
|
||||
|
||||
+4
-1
@@ -33,7 +33,10 @@ export const defineConsoleOutputTests = (
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'front-components-watch');
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { runAppDev } from '../../../../integration/utils/run-app-dev.util';
|
||||
import { type RunCliCommandResult } from '../../../../integration/utils/run-cli-command.util';
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { defineFrontComponentsTests } from './tests/front-components.tests';
|
||||
import { defineFunctionsTests } from './tests/functions.tests';
|
||||
|
||||
+7
-2
@@ -8,7 +8,9 @@ export const defineConsoleOutputTests = (
|
||||
it('should contain init messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'init');
|
||||
|
||||
expect(output).toContain('[init] 🚀 Starting Twenty Application Development Mode');
|
||||
expect(output).toContain(
|
||||
'[init] 🚀 Starting Twenty Application Development Mode',
|
||||
);
|
||||
expect(output).toContain('[init] 📁 App Path:');
|
||||
});
|
||||
|
||||
@@ -33,7 +35,10 @@ export const defineConsoleOutputTests = (
|
||||
});
|
||||
|
||||
it('should contain front-components-watch messages', () => {
|
||||
const output = getOutputByPrefix(getResult().output, 'front-components-watch');
|
||||
const output = getOutputByPrefix(
|
||||
getResult().output,
|
||||
'front-components-watch',
|
||||
);
|
||||
|
||||
expect(output).toContain('[front-components-watch] 🎨 Building...');
|
||||
expect(output).toContain('[front-components-watch] ✓ Built');
|
||||
|
||||
+4
-1
@@ -4,7 +4,10 @@ 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 frontComponentsDir = join(
|
||||
appPath,
|
||||
'.twenty/output/front-components',
|
||||
);
|
||||
const files = await fs.readdir(frontComponentsDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
|
||||
|
||||
+1
-4
@@ -8,10 +8,7 @@ export const defineFunctionsTests = (appPath: string): void => {
|
||||
const files = await fs.readdir(functionsDir, { recursive: true });
|
||||
const sortedFiles = files.map((f) => f.toString()).sort();
|
||||
|
||||
expect(sortedFiles).toEqual([
|
||||
'my.function.mjs',
|
||||
'my.function.mjs.map',
|
||||
]);
|
||||
expect(sortedFiles).toEqual(['my.function.mjs', 'my.function.mjs.map']);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+16
-5
@@ -16,14 +16,21 @@ 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 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(normalizeManifestForComparison({ functions: manifest.functions }).functions).toEqual(
|
||||
normalizeManifestForComparison({ functions: expected.functions }).functions,
|
||||
expect(
|
||||
normalizeManifestForComparison({ functions: manifest.functions })
|
||||
.functions,
|
||||
).toEqual(
|
||||
normalizeManifestForComparison({ functions: expected.functions })
|
||||
.functions,
|
||||
);
|
||||
|
||||
for (const fn of manifest.functions) {
|
||||
@@ -33,9 +40,13 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
}
|
||||
|
||||
expect(
|
||||
normalizeManifestForComparison({ frontComponents: manifest.frontComponents }).frontComponents,
|
||||
normalizeManifestForComparison({
|
||||
frontComponents: manifest.frontComponents,
|
||||
}).frontComponents,
|
||||
).toEqual(
|
||||
normalizeManifestForComparison({ frontComponents: expected.frontComponents }).frontComponents,
|
||||
normalizeManifestForComparison({
|
||||
frontComponents: expected.frontComponents,
|
||||
}).frontComponents,
|
||||
);
|
||||
|
||||
for (const component of manifest.frontComponents ?? []) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type TwentyConfig } from '@/cli/utilities/config/services/config.service';
|
||||
import { type TwentyConfig } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export const testConfig: TwentyConfig = {
|
||||
apiUrl: 'http://localhost:3000',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { testConfig } from '@/cli/__tests__/e2e/constants/testConfig';
|
||||
import { vi, beforeAll, afterAll } from 'vitest';
|
||||
|
||||
|
||||
+9
-2
@@ -1,6 +1,13 @@
|
||||
export type LogPrefix = 'init' | 'manifest-watch' | 'functions-watch' | 'front-components-watch';
|
||||
export type LogPrefix =
|
||||
| 'init'
|
||||
| 'manifest-watch'
|
||||
| 'functions-watch'
|
||||
| 'front-components-watch';
|
||||
|
||||
export const getOutputByPrefix = (output: string, prefix: LogPrefix): string => {
|
||||
export const getOutputByPrefix = (
|
||||
output: string,
|
||||
prefix: LogPrefix,
|
||||
): string => {
|
||||
const prefixPattern = `[${prefix}]`;
|
||||
const lines = output.split('\n');
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
// Loose type for JSON manifest imports where enum values are inferred as strings
|
||||
type JsonManifestInput = {
|
||||
functions?: Array<{ builtHandlerChecksum?: string | null; [key: string]: unknown }>;
|
||||
frontComponents?: Array<{ builtComponentChecksum?: string | null; [key: string]: unknown }>;
|
||||
functions?: Array<{
|
||||
builtHandlerChecksum?: string | null;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
frontComponents?: Array<{
|
||||
builtComponentChecksum?: string | null;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -20,4 +26,5 @@ export const normalizeManifestForComparison = <T extends JsonManifestInput>(
|
||||
? '[checksum]'
|
||||
: null,
|
||||
})),
|
||||
sources: {}, // removing sources for now, waiting compressed file implementation
|
||||
});
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { runCliCommand, type RunCliCommandResult } from './run-cli-command.util';
|
||||
import {
|
||||
runCliCommand,
|
||||
type RunCliCommandResult,
|
||||
} from './run-cli-command.util';
|
||||
|
||||
export type RunAppBuildOptions = {
|
||||
appPath: string;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export const runAppBuild = (options: RunAppBuildOptions): Promise<RunCliCommandResult> => {
|
||||
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
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { runCliCommand, type RunCliCommandResult } from './run-cli-command.util';
|
||||
import {
|
||||
runCliCommand,
|
||||
type RunCliCommandResult,
|
||||
} from './run-cli-command.util';
|
||||
|
||||
export type RunAppDevOptions = {
|
||||
appPath: string;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export const runAppDev = (options: RunAppDevOptions): Promise<RunCliCommandResult> => {
|
||||
export const runAppDev = (
|
||||
options: RunAppDevOptions,
|
||||
): Promise<RunCliCommandResult> => {
|
||||
const { appPath, timeout = 30000 } = options;
|
||||
|
||||
|
||||
return runCliCommand({
|
||||
command: 'app:dev',
|
||||
args: [appPath],
|
||||
|
||||
@@ -23,12 +23,7 @@ export type RunCliCommandResult = {
|
||||
export const runCliCommand = (
|
||||
options: RunCliCommandOptions,
|
||||
): Promise<RunCliCommandResult> => {
|
||||
const {
|
||||
command,
|
||||
args = [],
|
||||
waitForOutput,
|
||||
timeout = 30000,
|
||||
} = options;
|
||||
const { command, args = [], waitForOutput, timeout = 30000 } = options;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// Run from CLI directory to use twenty-sdk's tsconfig paths
|
||||
@@ -68,7 +63,7 @@ export const runCliCommand = (
|
||||
setTimeout(() => {
|
||||
child.kill();
|
||||
resolve({ success: true, output });
|
||||
}, 500);
|
||||
}, 1500);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
export const sanitizeOutput = (output: string): string => {
|
||||
return output
|
||||
// Remove ANSI color codes
|
||||
.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '')
|
||||
// Normalize file paths (replace any absolute path to a test app)
|
||||
.replace(/\/[^\s]+\/__tests__\/apps\/[^/]+/g, '<APP_PATH>')
|
||||
// Normalize timestamps
|
||||
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g, '<TIMESTAMP>')
|
||||
// Normalize durations
|
||||
.replace(/\d+ms/g, '<DURATION>')
|
||||
// Trim trailing whitespace from each line
|
||||
.split('\n')
|
||||
.map((line) => line.trimEnd())
|
||||
.join('\n')
|
||||
.trim();
|
||||
return (
|
||||
output
|
||||
// Remove ANSI color codes
|
||||
.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '')
|
||||
// Normalize file paths (replace any absolute path to a test app)
|
||||
.replace(/\/[^\s]+\/__tests__\/apps\/[^/]+/g, '<APP_PATH>')
|
||||
// Normalize timestamps
|
||||
.replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g, '<TIMESTAMP>')
|
||||
// Normalize durations
|
||||
.replace(/\d+ms/g, '<DURATION>')
|
||||
// Trim trailing whitespace from each line
|
||||
.split('\n')
|
||||
.map((line) => line.trimEnd())
|
||||
.join('\n')
|
||||
.trim()
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { registerCommands } from '@/cli/commands/app.command';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { registerCommands } from '@/cli/commands/app-command';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import chalk from 'chalk';
|
||||
import { Command, CommanderError } from 'commander';
|
||||
import { inspect } from 'util';
|
||||
|
||||
+3
-7
@@ -1,4 +1,4 @@
|
||||
import { formatPath } from '@/cli/utilities/file/utils/file-path';
|
||||
import { formatPath } from '@/cli/utilities/file/file-path';
|
||||
import chalk from 'chalk';
|
||||
import type { Command } from 'commander';
|
||||
import { AppBuildCommand } from './app/app-build';
|
||||
@@ -106,9 +106,7 @@ export const registerCommands = (program: Command): void => {
|
||||
.action(async (appPath?: string) => {
|
||||
try {
|
||||
const result = await syncCommand.execute(formatPath(appPath));
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(result.success ? 0 : 1);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -123,9 +121,7 @@ export const registerCommands = (program: Command): void => {
|
||||
appPath: formatPath(appPath),
|
||||
askForConfirmation: true,
|
||||
});
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(result.success ? 0 : 1);
|
||||
} catch {
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import { type ApiResponse } from '@/cli/utilities/api/types/api-response.types';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import { FrontComponentsWatcher } from '@/cli/utilities/build/front-components/front-component-watcher';
|
||||
import { FunctionsWatcher } from '@/cli/utilities/build/functions/function-watcher';
|
||||
import {
|
||||
type ManifestBuildResult,
|
||||
runManifestBuild,
|
||||
updateManifestChecksum,
|
||||
type ManifestBuildResult,
|
||||
} from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
|
||||
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
|
||||
const initLogger = createLogger('init');
|
||||
|
||||
@@ -23,7 +23,9 @@ export class AppBuildCommand {
|
||||
|
||||
private appPath: string = '';
|
||||
|
||||
async execute(options: AppBuildOptions): Promise<ApiResponse<null>> {
|
||||
async execute(
|
||||
options: AppBuildOptions,
|
||||
): Promise<ApiResponse<ManifestBuildResult>> {
|
||||
this.appPath = options.appPath ?? CURRENT_EXECUTION_DIRECTORY;
|
||||
|
||||
initLogger.log('🚀 Building Twenty Application');
|
||||
@@ -38,7 +40,7 @@ export class AppBuildCommand {
|
||||
|
||||
initLogger.success('✅ Build completed successfully');
|
||||
|
||||
return { success: true, data: null };
|
||||
return { success: true, data: buildResult };
|
||||
}
|
||||
|
||||
private async runBuild(): Promise<ManifestBuildResult | null> {
|
||||
@@ -56,7 +58,9 @@ export class AppBuildCommand {
|
||||
return buildResult;
|
||||
}
|
||||
|
||||
private async buildFunctions(buildResult: ManifestBuildResult): Promise<void> {
|
||||
private async buildFunctions(
|
||||
buildResult: ManifestBuildResult,
|
||||
): Promise<void> {
|
||||
this.functionsBuilder = new FunctionsWatcher({
|
||||
appPath: this.appPath,
|
||||
sourcePaths: buildResult.filePaths.functions,
|
||||
@@ -69,6 +73,7 @@ export class AppBuildCommand {
|
||||
builtPath,
|
||||
checksum,
|
||||
});
|
||||
|
||||
if (updatedManifest) {
|
||||
buildResult.manifest = updatedManifest;
|
||||
}
|
||||
@@ -79,7 +84,9 @@ export class AppBuildCommand {
|
||||
await this.functionsBuilder.start();
|
||||
}
|
||||
|
||||
private async buildFrontComponents(buildResult: ManifestBuildResult): Promise<void> {
|
||||
private async buildFrontComponents(
|
||||
buildResult: ManifestBuildResult,
|
||||
): Promise<void> {
|
||||
this.frontComponentsBuilder = new FrontComponentsWatcher({
|
||||
appPath: this.appPath,
|
||||
sourcePaths: buildResult.filePaths.frontComponents,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import { FrontComponentsWatcher } from '@/cli/utilities/build/front-components/front-component-watcher';
|
||||
import { FunctionsWatcher } from '@/cli/utilities/build/functions/function-watcher';
|
||||
import { runManifestBuild, updateManifestChecksum } from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import {
|
||||
runManifestBuild,
|
||||
updateManifestChecksum,
|
||||
} from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { ManifestWatcher } from '@/cli/utilities/build/manifest/manifest-watcher';
|
||||
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
|
||||
const initLogger = createLogger('init');
|
||||
@@ -69,10 +72,14 @@ export class AppDevCommand {
|
||||
|
||||
await this.startManifestWatcher();
|
||||
await this.startFunctionsWatcher(buildResult.filePaths.functions);
|
||||
await this.startFrontComponentsWatcher(buildResult.filePaths.frontComponents);
|
||||
await this.startFrontComponentsWatcher(
|
||||
buildResult.filePaths.frontComponents,
|
||||
);
|
||||
}
|
||||
|
||||
private initializeFunctionsFileUploadStatus(manifest: ApplicationManifest): void {
|
||||
private initializeFunctionsFileUploadStatus(
|
||||
manifest: ApplicationManifest,
|
||||
): void {
|
||||
this.state.fileStatusMaps.functions.clear();
|
||||
|
||||
for (const fn of manifest.functions ?? []) {
|
||||
@@ -85,16 +92,21 @@ export class AppDevCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private initializeFrontComponentsFileUploadStatus(manifest: ApplicationManifest): void {
|
||||
private initializeFrontComponentsFileUploadStatus(
|
||||
manifest: ApplicationManifest,
|
||||
): void {
|
||||
this.state.fileStatusMaps.frontComponents.clear();
|
||||
|
||||
for (const component of manifest.frontComponents ?? []) {
|
||||
this.state.fileStatusMaps.frontComponents.set(component.universalIdentifier, {
|
||||
sourcePath: component.sourceComponentPath,
|
||||
builtPath: component.builtComponentPath,
|
||||
checksum: null,
|
||||
isUploaded: false,
|
||||
});
|
||||
this.state.fileStatusMaps.frontComponents.set(
|
||||
component.universalIdentifier,
|
||||
{
|
||||
sourcePath: component.sourceComponentPath,
|
||||
builtPath: component.builtComponentPath,
|
||||
checksum: null,
|
||||
isUploaded: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +118,8 @@ export class AppDevCommand {
|
||||
this.state.manifest = result.manifest;
|
||||
|
||||
const functionSourcePaths = result.filePaths.functions;
|
||||
const shouldRestartFunctions = this.functionsWatcher?.shouldRestart(functionSourcePaths);
|
||||
const shouldRestartFunctions =
|
||||
this.functionsWatcher?.shouldRestart(functionSourcePaths);
|
||||
if (shouldRestartFunctions) {
|
||||
if (result.manifest) {
|
||||
this.initializeFunctionsFileUploadStatus(result.manifest);
|
||||
@@ -115,7 +128,8 @@ export class AppDevCommand {
|
||||
}
|
||||
|
||||
const componentSourcePaths = result.filePaths.frontComponents;
|
||||
const shouldRestartFrontComponents = this.frontComponentsWatcher?.shouldRestart(componentSourcePaths);
|
||||
const shouldRestartFrontComponents =
|
||||
this.frontComponentsWatcher?.shouldRestart(componentSourcePaths);
|
||||
if (shouldRestartFrontComponents) {
|
||||
if (result.manifest) {
|
||||
this.initializeFrontComponentsFileUploadStatus(result.manifest);
|
||||
@@ -133,34 +147,37 @@ export class AppDevCommand {
|
||||
this.functionsWatcher = new FunctionsWatcher({
|
||||
appPath: this.appPath,
|
||||
sourcePaths,
|
||||
onFileBuilt: (builtPath, checksum) => {
|
||||
this.updateFileStatus('function', builtPath, checksum);
|
||||
onFileBuilt: async (builtPath, checksum) => {
|
||||
await this.updateFileStatus('function', builtPath, checksum);
|
||||
},
|
||||
});
|
||||
|
||||
await this.functionsWatcher.start();
|
||||
}
|
||||
|
||||
private async startFrontComponentsWatcher(sourcePaths: string[]): Promise<void> {
|
||||
private async startFrontComponentsWatcher(
|
||||
sourcePaths: string[],
|
||||
): Promise<void> {
|
||||
this.frontComponentsWatcher = new FrontComponentsWatcher({
|
||||
appPath: this.appPath,
|
||||
sourcePaths,
|
||||
onFileBuilt: (builtPath, checksum) => {
|
||||
this.updateFileStatus('frontComponent', builtPath, checksum);
|
||||
onFileBuilt: async (builtPath, checksum) => {
|
||||
await this.updateFileStatus('frontComponent', builtPath, checksum);
|
||||
},
|
||||
});
|
||||
|
||||
await this.frontComponentsWatcher.start();
|
||||
}
|
||||
|
||||
private updateFileStatus(
|
||||
private async updateFileStatus(
|
||||
entityType: 'function' | 'frontComponent',
|
||||
builtPath: string,
|
||||
checksum: string,
|
||||
): void {
|
||||
const statusMap = entityType === 'function'
|
||||
? this.state.fileStatusMaps.functions
|
||||
: this.state.fileStatusMaps.frontComponents;
|
||||
): Promise<void> {
|
||||
const statusMap =
|
||||
entityType === 'function'
|
||||
? this.state.fileStatusMaps.functions
|
||||
: this.state.fileStatusMaps.frontComponents;
|
||||
|
||||
for (const [_id, status] of statusMap) {
|
||||
if (status.builtPath === builtPath) {
|
||||
@@ -172,10 +189,33 @@ export class AppDevCommand {
|
||||
|
||||
const manifest = this.state.manifest;
|
||||
if (manifest) {
|
||||
const updatedManifest = updateManifestChecksum({ manifest, entityType, builtPath, checksum });
|
||||
const updatedManifest = updateManifestChecksum({
|
||||
manifest,
|
||||
entityType,
|
||||
builtPath,
|
||||
checksum,
|
||||
});
|
||||
if (updatedManifest) {
|
||||
this.state.manifest = updatedManifest;
|
||||
writeManifestToOutput(this.appPath, updatedManifest);
|
||||
await writeManifestToOutput(this.appPath, updatedManifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private markFileAsUploaded(
|
||||
entityType: 'function' | 'frontComponent',
|
||||
builtPath: string,
|
||||
success: boolean,
|
||||
): void {
|
||||
const statusMap =
|
||||
entityType === 'function'
|
||||
? this.state.fileStatusMaps.functions
|
||||
: this.state.fileStatusMaps.frontComponents;
|
||||
|
||||
for (const [_id, status] of statusMap) {
|
||||
if (status.builtPath === builtPath) {
|
||||
status.isUploaded = success;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import chalk from 'chalk';
|
||||
import { GenerateService } from '@/cli/utilities/generate/services/generate.service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { ClientService } from '@/cli/utilities/client/client-service';
|
||||
|
||||
export class AppGenerateCommand {
|
||||
private generateService = new GenerateService();
|
||||
private clientService = new ClientService();
|
||||
|
||||
async execute(appPath: string = CURRENT_EXECUTION_DIRECTORY) {
|
||||
try {
|
||||
await this.generateService.generateClient(appPath);
|
||||
await this.clientService.generate(appPath);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('Generate Twenty client failed:'),
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/types/api-response.types';
|
||||
import { runManifestBuild } from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { AppBuildCommand } from '@/cli/commands/app/app-build';
|
||||
import { FileUploader } from '@/cli/utilities/file/file-uploader';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
|
||||
export class AppSyncCommand {
|
||||
private apiService = new ApiService();
|
||||
private buildCommand = new AppBuildCommand();
|
||||
|
||||
async execute(
|
||||
appPath: string = CURRENT_EXECUTION_DIRECTORY,
|
||||
): Promise<ApiResponse<any>> {
|
||||
console.log(chalk.blue('🚀 Syncing Twenty Application'));
|
||||
console.log(chalk.gray(`📁 App Path: ${appPath}`));
|
||||
console.log('');
|
||||
|
||||
const { manifest } = await runManifestBuild(appPath, { writeOutput: false });
|
||||
const result = await this.buildCommand.execute({
|
||||
appPath,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const manifest = result.data.manifest;
|
||||
|
||||
if (!manifest) {
|
||||
return { success: false, error: 'Build failed' };
|
||||
return { success: false, error: 'No manifest found. Build failed?' };
|
||||
}
|
||||
|
||||
const uploadService = new FileUploader({
|
||||
applicationUniversalIdentifier: manifest.application.universalIdentifier,
|
||||
appPath,
|
||||
});
|
||||
|
||||
await uploadService.uploadManifestBuiltFiles(manifest);
|
||||
|
||||
const yarnLockPath = path.join(appPath, 'yarn.lock');
|
||||
let yarnLock = '';
|
||||
|
||||
@@ -29,20 +45,17 @@ export class AppSyncCommand {
|
||||
yarnLock = await fs.readFile(yarnLockPath, 'utf8');
|
||||
}
|
||||
|
||||
const serverlessSyncResult = await this.apiService.syncApplication({
|
||||
const syncResult = await this.apiService.syncApplication({
|
||||
manifest,
|
||||
yarnLock,
|
||||
});
|
||||
|
||||
if (serverlessSyncResult.success === false) {
|
||||
console.error(
|
||||
chalk.red('❌ Application Sync failed:'),
|
||||
serverlessSyncResult.error,
|
||||
);
|
||||
if (!syncResult.success) {
|
||||
console.error(chalk.red('❌ Application Sync failed:'), syncResult.error);
|
||||
} else {
|
||||
console.log(chalk.green('✅ Application synced successfully'));
|
||||
}
|
||||
|
||||
return serverlessSyncResult;
|
||||
return syncResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/types/api-response.types';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import { runManifestBuild } from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
@@ -25,7 +25,10 @@ export class AppUninstallCommand {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { manifest } = await runManifestBuild(appPath, { display: false, writeOutput: false });
|
||||
const { manifest } = await runManifestBuild(appPath, {
|
||||
display: false,
|
||||
writeOutput: false,
|
||||
});
|
||||
|
||||
if (!manifest) {
|
||||
return { success: false, error: 'Build failed' };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export class AuthListCommand {
|
||||
private configService = new ConfigService();
|
||||
@@ -22,7 +22,8 @@ export class AuthListCommand {
|
||||
console.log(chalk.blue('Available workspaces:\n'));
|
||||
|
||||
for (const workspace of availableWorkspaces) {
|
||||
const config = await this.configService.getConfigForWorkspace(workspace);
|
||||
const config =
|
||||
await this.configService.getConfigForWorkspace(workspace);
|
||||
const hasCredentials = !!config.apiKey;
|
||||
const isDefault = workspace === currentDefault;
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export class AuthLoginCommand {
|
||||
private configService = new ConfigService();
|
||||
private apiService = new ApiService();
|
||||
|
||||
async execute(options: {
|
||||
apiKey?: string;
|
||||
apiUrl?: string;
|
||||
}): Promise<void> {
|
||||
async execute(options: { apiKey?: string; apiUrl?: string }): Promise<void> {
|
||||
try {
|
||||
let { apiKey, apiUrl } = options;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export class AuthLogoutCommand {
|
||||
private configService = new ConfigService();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import chalk from 'chalk';
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export class AuthStatusCommand {
|
||||
private configService = new ConfigService();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export class AuthSwitchCommand {
|
||||
private configService = new ConfigService();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { getFrontComponentBaseFile } from '@/cli/utilities/entity/utils/entity-front-component-template';
|
||||
import { getFunctionBaseFile } from '@/cli/utilities/entity/utils/entity-function-template';
|
||||
import { convertToLabel } from '@/cli/utilities/entity/utils/entity-label';
|
||||
import { getNewObjectFileContent } from '@/cli/utilities/entity/utils/entity-object-template';
|
||||
import { getRoleBaseFile } from '@/cli/utilities/entity/utils/entity-role-template';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { getFrontComponentBaseFile } from '@/cli/utilities/entity/entity-front-component-template';
|
||||
import { getFunctionBaseFile } from '@/cli/utilities/entity/entity-function-template';
|
||||
import { convertToLabel } from '@/cli/utilities/entity/entity-label';
|
||||
import { getNewObjectFileContent } from '@/cli/utilities/entity/entity-object-template';
|
||||
import { getRoleBaseFile } from '@/cli/utilities/entity/entity-role-template';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { runManifestBuild } from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { runManifestBuild } from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
|
||||
export class FunctionLogsCommand {
|
||||
@@ -16,7 +16,10 @@ export class FunctionLogsCommand {
|
||||
functionName?: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { manifest } = await runManifestBuild(appPath, { display: false, writeOutput: false });
|
||||
const { manifest } = await runManifestBuild(appPath, {
|
||||
display: false,
|
||||
writeOutput: false,
|
||||
});
|
||||
|
||||
if (!manifest) {
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Placeholder for sdk generate command
|
||||
// TODO: Implement SDK generation functionality
|
||||
|
||||
export class SdkGenerateCommand {
|
||||
async execute(): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Directory name for static assets in Twenty applications.
|
||||
* Assets are copied from this folder at the root of the app to the build output.
|
||||
*/
|
||||
export const ASSETS_DIR = 'assets';
|
||||
+11
-5
@@ -1,4 +1,4 @@
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import axios, { type AxiosInstance, type AxiosResponse } from 'axios';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs';
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
import * as path from 'path';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
import { type FileFolder } from 'twenty-shared/types';
|
||||
import { type ApiResponse } from '../types/api-response.types';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import { pascalCase } from 'twenty-shared/utils';
|
||||
|
||||
export class ApiService {
|
||||
private client: AxiosInstance;
|
||||
@@ -271,7 +272,8 @@ export class ApiService {
|
||||
if (response.data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
error: response.data.errors[0]?.message || 'Failed to fetch functions',
|
||||
error:
|
||||
response.data.errors[0]?.message || 'Failed to fetch functions',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -415,10 +417,12 @@ export class ApiService {
|
||||
|
||||
async uploadFile({
|
||||
filePath,
|
||||
builtHandlerPath,
|
||||
fileFolder,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
filePath: string;
|
||||
builtHandlerPath: string;
|
||||
fileFolder: FileFolder;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<ApiResponse<boolean>> {
|
||||
@@ -443,13 +447,15 @@ export class ApiService {
|
||||
}
|
||||
`;
|
||||
|
||||
const graphqlEnumFileFolder = pascalCase(fileFolder);
|
||||
|
||||
const operations = JSON.stringify({
|
||||
query: mutation,
|
||||
variables: {
|
||||
file: null,
|
||||
applicationUniversalIdentifier,
|
||||
filePath,
|
||||
fileFolder,
|
||||
filePath: builtHandlerPath,
|
||||
fileFolder: graphqlEnumFileFolder,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
||||
import type * as esbuild from 'esbuild';
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { type OnFileBuiltCallback } from './restartable-watcher.interface';
|
||||
import { type OnFileBuiltCallback } from '@/cli/utilities/build/common/restartable-watcher-interface';
|
||||
|
||||
export type ProcessEsbuildResultParams = {
|
||||
result: esbuild.BuildResult;
|
||||
@@ -25,8 +25,9 @@ export const processEsbuildResult = async ({
|
||||
onFileBuilt,
|
||||
onSuccess,
|
||||
}: ProcessEsbuildResultParams): Promise<ProcessEsbuildResultOutput> => {
|
||||
const outputFiles = Object.keys(result.metafile?.outputs ?? {})
|
||||
.filter((file) => file.endsWith('.mjs'));
|
||||
const outputFiles = Object.keys(result.metafile?.outputs ?? {}).filter(
|
||||
(file) => file.endsWith('.mjs'),
|
||||
);
|
||||
|
||||
let hasChanges = false;
|
||||
|
||||
@@ -50,7 +51,7 @@ export const processEsbuildResult = async ({
|
||||
onSuccess(relativePath);
|
||||
|
||||
if (onFileBuilt) {
|
||||
onFileBuilt(builtPath, checksum);
|
||||
await onFileBuilt(builtPath, checksum);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ export type LoggerContext =
|
||||
| 'init'
|
||||
| 'manifest-watch'
|
||||
| 'functions-watch'
|
||||
| 'front-components-watch';
|
||||
| 'front-components-watch'
|
||||
| 'file-upload';
|
||||
|
||||
type LoggerConfig = {
|
||||
prefix: string;
|
||||
@@ -28,6 +29,10 @@ const LOGGER_CONFIGS: Record<LoggerContext, LoggerConfig> = {
|
||||
prefix: '[front-components-watch]',
|
||||
color: chalk.green,
|
||||
},
|
||||
'file-upload': {
|
||||
prefix: '[file-upload]',
|
||||
color: chalk.blue,
|
||||
},
|
||||
};
|
||||
|
||||
export type Logger = {
|
||||
@@ -43,8 +48,11 @@ export const createLogger = (context: LoggerContext): Logger => {
|
||||
|
||||
return {
|
||||
log: (message: string) => console.log(`${prefix} ${message}`),
|
||||
success: (message: string) => console.log(`${prefix} ${chalk.green(message)}`),
|
||||
error: (message: string) => console.error(`${prefix} ${chalk.red(message)}`),
|
||||
warn: (message: string) => console.log(`${prefix} ${chalk.yellow(message)}`),
|
||||
success: (message: string) =>
|
||||
console.log(`${prefix} ${chalk.green(message)}`),
|
||||
error: (message: string) =>
|
||||
console.error(`${prefix} ${chalk.red(message)}`),
|
||||
warn: (message: string) =>
|
||||
console.log(`${prefix} ${chalk.yellow(message)}`),
|
||||
};
|
||||
};
|
||||
|
||||
+4
-1
@@ -5,7 +5,10 @@ export interface RestartableWatcher {
|
||||
shouldRestart(sourcePaths: string[]): boolean;
|
||||
}
|
||||
|
||||
export type OnFileBuiltCallback = (builtPath: string, checksum: string) => void;
|
||||
export type OnFileBuiltCallback = (
|
||||
builtPath: string,
|
||||
checksum: string,
|
||||
) => void | Promise<void>;
|
||||
|
||||
export type RestartableWatcherOptions = {
|
||||
appPath: string;
|
||||
+13
-8
@@ -1,16 +1,16 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { cleanupRemovedFiles } from '../common/cleanup-removed-files';
|
||||
import { OUTPUT_DIR } from '../common/constants';
|
||||
import { createLogger } from '../common/logger';
|
||||
import { processEsbuildResult } from '../common/esbuild-result-processor';
|
||||
import { cleanupRemovedFiles } from '@/cli/utilities/build/common/cleanup-removed-files';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
import { processEsbuildResult } from '@/cli/utilities/build/common/esbuild-result-processor';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import {
|
||||
type OnFileBuiltCallback,
|
||||
type RestartableWatcher,
|
||||
type RestartableWatcherOptions,
|
||||
} from '../common/restartable-watcher.interface';
|
||||
import { FRONT_COMPONENTS_DIR } from './constants';
|
||||
} from '@/cli/utilities/build/common/restartable-watcher-interface';
|
||||
import { FRONT_COMPONENTS_DIR } from '@/cli/utilities/build/front-components/constants';
|
||||
|
||||
const logger = createLogger('front-components-watch');
|
||||
|
||||
@@ -78,7 +78,11 @@ export class FrontComponentsWatcher implements RestartableWatcher {
|
||||
logger.warn('🔄 Restarting...');
|
||||
await this.close();
|
||||
|
||||
const outputDir = path.join(this.appPath, OUTPUT_DIR, FRONT_COMPONENTS_DIR);
|
||||
const outputDir = path.join(
|
||||
this.appPath,
|
||||
OUTPUT_DIR,
|
||||
FRONT_COMPONENTS_DIR,
|
||||
);
|
||||
await cleanupRemovedFiles(outputDir, this.componentPaths, sourcePaths);
|
||||
this.componentPaths = sourcePaths;
|
||||
this.lastChecksums.clear();
|
||||
@@ -142,7 +146,8 @@ export class FrontComponentsWatcher implements RestartableWatcher {
|
||||
builtDir: FRONT_COMPONENTS_DIR,
|
||||
lastChecksums: watcher.lastChecksums,
|
||||
onFileBuilt: watcher.onFileBuilt,
|
||||
onSuccess: (relativePath) => logger.success(`✓ Built ${relativePath}`),
|
||||
onSuccess: (relativePath) =>
|
||||
logger.success(`✓ Built ${relativePath}`),
|
||||
});
|
||||
|
||||
if (hasChanges && watchMode) {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { cleanupRemovedFiles } from '../common/cleanup-removed-files';
|
||||
import { OUTPUT_DIR } from '../common/constants';
|
||||
import { createLogger } from '../common/logger';
|
||||
import { processEsbuildResult } from '../common/esbuild-result-processor';
|
||||
import { cleanupRemovedFiles } from '@/cli/utilities/build/common/cleanup-removed-files';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
import { processEsbuildResult } from '@/cli/utilities/build/common/esbuild-result-processor';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import {
|
||||
type OnFileBuiltCallback,
|
||||
type RestartableWatcher,
|
||||
type RestartableWatcherOptions,
|
||||
} from '../common/restartable-watcher.interface';
|
||||
import { FUNCTIONS_DIR } from './constants';
|
||||
} from '@/cli/utilities/build/common/restartable-watcher-interface';
|
||||
import { FUNCTIONS_DIR } from '@/cli/utilities/build/functions/constants';
|
||||
|
||||
const logger = createLogger('functions-watch');
|
||||
|
||||
@@ -140,10 +140,13 @@ export class FunctionsWatcher implements RestartableWatcher {
|
||||
{
|
||||
name: 'external-patterns',
|
||||
setup: (build) => {
|
||||
build.onResolve({ filter: /(?:^|\/)generated(?:\/|$)/ }, (args) => ({
|
||||
path: args.path,
|
||||
external: true,
|
||||
}));
|
||||
build.onResolve(
|
||||
{ filter: /(?:^|\/)generated(?:\/|$)/ },
|
||||
(args) => ({
|
||||
path: args.path,
|
||||
external: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -165,7 +168,8 @@ export class FunctionsWatcher implements RestartableWatcher {
|
||||
builtDir: FUNCTIONS_DIR,
|
||||
lastChecksums: watcher.lastChecksums,
|
||||
onFileBuilt: watcher.onFileBuilt,
|
||||
onSuccess: (relativePath) => logger.success(`✓ Built ${relativePath}`),
|
||||
onSuccess: (relativePath) =>
|
||||
logger.success(`✓ Built ${relativePath}`),
|
||||
});
|
||||
|
||||
if (hasChanges && watchMode) {
|
||||
|
||||
+15
@@ -31,6 +31,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
const result = validateManifest({
|
||||
application: validApplication,
|
||||
objects: [],
|
||||
frontComponents: [],
|
||||
objectExtensions: [validObjectExtension],
|
||||
functions: [],
|
||||
roles: [],
|
||||
@@ -60,6 +61,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [extensionByUuid],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -87,6 +89,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [validObjectExtension, anotherExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -123,6 +126,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [extensionWithSelect],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -142,6 +146,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -164,6 +169,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -190,6 +196,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -217,6 +224,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -247,6 +255,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -277,6 +286,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -307,6 +317,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -338,6 +349,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -370,6 +382,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [invalidExtension],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -411,6 +424,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
objects: [],
|
||||
objectExtensions: [extensionWithDuplicates],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
@@ -458,6 +472,7 @@ describe('validateManifest - objectExtensions', () => {
|
||||
},
|
||||
],
|
||||
functions: [],
|
||||
frontComponents: [],
|
||||
roles: [],
|
||||
});
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
} from 'twenty-shared/application';
|
||||
import { createLogger } from '../../common/logger';
|
||||
import { manifestExtractFromFileServer } from '../manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import {
|
||||
type EntityBuildResult,
|
||||
type EntityIdWithLocation,
|
||||
type ManifestEntityBuilder,
|
||||
type ManifestWithoutSources,
|
||||
} from './entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
|
||||
export type EntityIdWithLocation = {
|
||||
id: string;
|
||||
@@ -1,21 +1,25 @@
|
||||
import { glob } from 'fast-glob';
|
||||
import { type FrontComponentManifest } from 'twenty-shared/application';
|
||||
import { createLogger } from '../../common/logger';
|
||||
import { FRONT_COMPONENTS_DIR } from '../../front-components/constants';
|
||||
import { manifestExtractFromFileServer } from '../manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
|
||||
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import {
|
||||
type EntityBuildResult,
|
||||
type EntityIdWithLocation,
|
||||
type ManifestEntityBuilder,
|
||||
type ManifestWithoutSources,
|
||||
} from './entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
import { FRONT_COMPONENTS_DIR } from '@/cli/utilities/build/front-components/constants';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
type FrontComponentConfig = Omit<
|
||||
FrontComponentManifest,
|
||||
'sourceComponentPath' | 'builtComponentPath' | 'builtComponentChecksum' | 'componentName'
|
||||
| 'sourceComponentPath'
|
||||
| 'builtComponentPath'
|
||||
| 'builtComponentChecksum'
|
||||
| 'componentName'
|
||||
> & {
|
||||
component: { name: string };
|
||||
};
|
||||
@@ -23,10 +27,17 @@ type FrontComponentConfig = Omit<
|
||||
export class FrontComponentEntityBuilder
|
||||
implements ManifestEntityBuilder<FrontComponentManifest>
|
||||
{
|
||||
async build(appPath: string): Promise<EntityBuildResult<FrontComponentManifest>> {
|
||||
async build(
|
||||
appPath: string,
|
||||
): Promise<EntityBuildResult<FrontComponentManifest>> {
|
||||
const componentFiles = await glob(['**/*.front-component.tsx'], {
|
||||
cwd: appPath,
|
||||
ignore: ['**/node_modules/**', '**/*.d.ts', '**/dist/**', '**/.twenty/**'],
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'**/*.d.ts',
|
||||
'**/dist/**',
|
||||
'**/.twenty/**',
|
||||
],
|
||||
});
|
||||
|
||||
const manifests: FrontComponentManifest[] = [];
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { glob } from 'fast-glob';
|
||||
import { type ServerlessFunctionManifest } from 'twenty-shared/application';
|
||||
import { createLogger } from '../../common/logger';
|
||||
import { FUNCTIONS_DIR } from '../../functions/constants';
|
||||
import { manifestExtractFromFileServer } from '../manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
|
||||
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import {
|
||||
type EntityBuildResult,
|
||||
type EntityIdWithLocation,
|
||||
type ManifestEntityBuilder,
|
||||
type ManifestWithoutSources,
|
||||
} from './entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
import { FUNCTIONS_DIR } from '@/cli/utilities/build/functions/constants';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
@@ -23,10 +24,17 @@ type ExtractedFunctionManifest = Omit<
|
||||
export class FunctionEntityBuilder
|
||||
implements ManifestEntityBuilder<ServerlessFunctionManifest>
|
||||
{
|
||||
async build(appPath: string): Promise<EntityBuildResult<ServerlessFunctionManifest>> {
|
||||
async build(
|
||||
appPath: string,
|
||||
): Promise<EntityBuildResult<ServerlessFunctionManifest>> {
|
||||
const functionFiles = await glob(['**/*.function.ts'], {
|
||||
cwd: appPath,
|
||||
ignore: ['**/node_modules/**', '**/*.d.ts', '**/dist/**', '**/.twenty/**'],
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'**/*.d.ts',
|
||||
'**/dist/**',
|
||||
'**/.twenty/**',
|
||||
],
|
||||
});
|
||||
|
||||
const manifests: ServerlessFunctionManifest[] = [];
|
||||
|
||||
@@ -2,22 +2,29 @@ import { glob } from 'fast-glob';
|
||||
import { type ObjectExtensionManifest } from 'twenty-shared/application';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { manifestExtractFromFileServer } from '../manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import {
|
||||
type EntityBuildResult,
|
||||
type EntityIdWithLocation,
|
||||
type ManifestEntityBuilder,
|
||||
type ManifestWithoutSources,
|
||||
} from './entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
|
||||
export class ObjectExtensionEntityBuilder
|
||||
implements ManifestEntityBuilder<ObjectExtensionManifest>
|
||||
{
|
||||
async build(appPath: string): Promise<EntityBuildResult<ObjectExtensionManifest>> {
|
||||
async build(
|
||||
appPath: string,
|
||||
): Promise<EntityBuildResult<ObjectExtensionManifest>> {
|
||||
const extensionFiles = await glob(['**/*.object-extension.ts'], {
|
||||
cwd: appPath,
|
||||
ignore: ['**/node_modules/**', '**/*.d.ts', '**/dist/**', '**/.twenty/**'],
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'**/*.d.ts',
|
||||
'**/dist/**',
|
||||
'**/.twenty/**',
|
||||
],
|
||||
});
|
||||
|
||||
const manifests: ObjectExtensionManifest[] = [];
|
||||
|
||||
@@ -2,15 +2,15 @@ import { glob } from 'fast-glob';
|
||||
import { type ObjectManifest } from 'twenty-shared/application';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { createLogger } from '../../common/logger';
|
||||
import { manifestExtractFromFileServer } from '../manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import {
|
||||
type EntityBuildResult,
|
||||
type EntityIdWithLocation,
|
||||
type ManifestEntityBuilder,
|
||||
type ManifestWithoutSources,
|
||||
} from './entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
@@ -20,7 +20,12 @@ export class ObjectEntityBuilder
|
||||
async build(appPath: string): Promise<EntityBuildResult<ObjectManifest>> {
|
||||
const objectFiles = await glob(['**/*.object.ts'], {
|
||||
cwd: appPath,
|
||||
ignore: ['**/node_modules/**', '**/*.d.ts', '**/dist/**', '**/.twenty/**'],
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'**/*.d.ts',
|
||||
'**/dist/**',
|
||||
'**/.twenty/**',
|
||||
],
|
||||
});
|
||||
|
||||
const manifests: ObjectManifest[] = [];
|
||||
@@ -30,7 +35,9 @@ export class ObjectEntityBuilder
|
||||
const absolutePath = `${appPath}/${filePath}`;
|
||||
|
||||
manifests.push(
|
||||
await manifestExtractFromFileServer.extractManifestFromFile<ObjectManifest>(absolutePath),
|
||||
await manifestExtractFromFileServer.extractManifestFromFile<ObjectManifest>(
|
||||
absolutePath,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { glob } from 'fast-glob';
|
||||
import { type RoleManifest } from 'twenty-shared/application';
|
||||
import { createLogger } from '../../common/logger';
|
||||
import { manifestExtractFromFileServer } from '../manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '../manifest.types';
|
||||
import { manifestExtractFromFileServer } from '@/cli/utilities/build/manifest/manifest-extract-from-file-server';
|
||||
import { type ValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import {
|
||||
type EntityBuildResult,
|
||||
type EntityIdWithLocation,
|
||||
type ManifestEntityBuilder,
|
||||
type ManifestWithoutSources,
|
||||
} from './entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
@@ -16,7 +16,12 @@ export class RoleEntityBuilder implements ManifestEntityBuilder<RoleManifest> {
|
||||
async build(appPath: string): Promise<EntityBuildResult<RoleManifest>> {
|
||||
const roleFiles = await glob(['**/*.role.ts'], {
|
||||
cwd: appPath,
|
||||
ignore: ['**/node_modules/**', '**/*.d.ts', '**/dist/**', '**/.twenty/**'],
|
||||
ignore: [
|
||||
'**/node_modules/**',
|
||||
'**/*.d.ts',
|
||||
'**/dist/**',
|
||||
'**/.twenty/**',
|
||||
],
|
||||
});
|
||||
|
||||
const manifests: RoleManifest[] = [];
|
||||
@@ -26,7 +31,9 @@ export class RoleEntityBuilder implements ManifestEntityBuilder<RoleManifest> {
|
||||
const absolutePath = `${appPath}/${filePath}`;
|
||||
|
||||
manifests.push(
|
||||
await manifestExtractFromFileServer.extractManifestFromFile<RoleManifest>(absolutePath),
|
||||
await manifestExtractFromFileServer.extractManifestFromFile<RoleManifest>(
|
||||
absolutePath,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { findPathFile } from '@/cli/utilities/file/utils/file-find';
|
||||
import { parseJsoncFile } from '@/cli/utilities/file/utils/file-jsonc';
|
||||
import { findPathFile } from '@/cli/utilities/file/file-find';
|
||||
import { parseJsoncFile } from '@/cli/utilities/file/file-jsonc';
|
||||
import { glob } from 'fast-glob';
|
||||
import * as fs from 'fs-extra';
|
||||
import { relative, sep } from 'path';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
import { type Sources } from 'twenty-shared/types';
|
||||
import { createLogger } from '../common/logger';
|
||||
import { applicationEntityBuilder } from './entities/application';
|
||||
import { frontComponentEntityBuilder } from './entities/front-component';
|
||||
import { functionEntityBuilder } from './entities/function';
|
||||
import { objectEntityBuilder } from './entities/object';
|
||||
import { objectExtensionEntityBuilder } from './entities/object-extension';
|
||||
import { roleEntityBuilder } from './entities/role';
|
||||
import { displayEntitySummary, displayErrors, displayWarnings } from './manifest-display';
|
||||
import { applicationEntityBuilder } from '@/cli/utilities/build/manifest/entities/application';
|
||||
import { frontComponentEntityBuilder } from '@/cli/utilities/build/manifest/entities/front-component';
|
||||
import { functionEntityBuilder } from '@/cli/utilities/build/manifest/entities/function';
|
||||
import { objectEntityBuilder } from '@/cli/utilities/build/manifest/entities/object';
|
||||
import { objectExtensionEntityBuilder } from '@/cli/utilities/build/manifest/entities/object-extension';
|
||||
import { roleEntityBuilder } from '@/cli/utilities/build/manifest/entities/role';
|
||||
import {
|
||||
displayEntitySummary,
|
||||
displayErrors,
|
||||
displayWarnings,
|
||||
} from '@/cli/utilities/build/manifest/manifest-display';
|
||||
import { manifestExtractFromFileServer } from './manifest-extract-from-file-server';
|
||||
import { validateManifest } from './manifest-validate';
|
||||
import { writeManifestToOutput } from './manifest-writer';
|
||||
import { ManifestValidationError } from './manifest.types';
|
||||
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
|
||||
import { ManifestValidationError } from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import { validateManifest } from '@/cli/utilities/build/manifest/manifest-validate';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
@@ -93,7 +97,9 @@ export const updateManifestChecksum = ({
|
||||
checksum,
|
||||
}: UpdateManifestChecksumParams): ApplicationManifest | null => {
|
||||
if (entityType === 'function') {
|
||||
const fnIndex = manifest.functions.findIndex((f) => f.builtHandlerPath === builtPath);
|
||||
const fnIndex = manifest.functions.findIndex(
|
||||
(f) => f.builtHandlerPath === builtPath,
|
||||
);
|
||||
if (fnIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
@@ -105,16 +111,19 @@ export const updateManifestChecksum = ({
|
||||
};
|
||||
}
|
||||
|
||||
const componentIndex = manifest.frontComponents?.findIndex(
|
||||
(c) => c.builtComponentPath === builtPath,
|
||||
) ?? -1;
|
||||
const componentIndex =
|
||||
manifest.frontComponents.findIndex(
|
||||
(c) => c.builtComponentPath === builtPath,
|
||||
) ?? -1;
|
||||
if (componentIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...manifest,
|
||||
frontComponents: manifest.frontComponents?.map((component, index) =>
|
||||
index === componentIndex ? { ...component, builtComponentChecksum: checksum } : component,
|
||||
frontComponents: manifest.frontComponents.map((component, index) =>
|
||||
index === componentIndex
|
||||
? { ...component, builtComponentChecksum: checksum }
|
||||
: component,
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -173,11 +182,9 @@ export const runManifestBuild = async (
|
||||
const manifest: ApplicationManifest = {
|
||||
application,
|
||||
objects: objectManifests,
|
||||
objectExtensions:
|
||||
objectExtensionManifests.length > 0 ? objectExtensionManifests : undefined,
|
||||
objectExtensions: objectExtensionManifests,
|
||||
functions: functionManifests,
|
||||
frontComponents:
|
||||
frontComponentManifests.length > 0 ? frontComponentManifests : undefined,
|
||||
frontComponents: frontComponentManifests,
|
||||
roles: roleManifests,
|
||||
sources,
|
||||
packageJson,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
import { createLogger } from '../common/logger';
|
||||
import { applicationEntityBuilder } from './entities/application';
|
||||
import { frontComponentEntityBuilder } from './entities/front-component';
|
||||
import { functionEntityBuilder } from './entities/function';
|
||||
import { objectEntityBuilder } from './entities/object';
|
||||
import { roleEntityBuilder } from './entities/role';
|
||||
import { type ManifestValidationError, type ValidationWarning } from './manifest.types';
|
||||
import { frontComponentEntityBuilder } from '@/cli/utilities/build/manifest/entities/front-component';
|
||||
import { functionEntityBuilder } from '@/cli/utilities/build/manifest/entities/function';
|
||||
import { objectEntityBuilder } from '@/cli/utilities/build/manifest/entities/object';
|
||||
import { roleEntityBuilder } from '@/cli/utilities/build/manifest/entities/role';
|
||||
import {
|
||||
type ManifestValidationError,
|
||||
type ValidationWarning,
|
||||
} from '@/cli/utilities/build/manifest/manifest-types';
|
||||
import { applicationEntityBuilder } from '@/cli/utilities/build/manifest/entities/application';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
|
||||
+39
-12
@@ -24,14 +24,22 @@ export class ManifestExtractFromFileServer {
|
||||
options: ExtractManifestOptions = {},
|
||||
): Promise<TManifest> {
|
||||
if (!this.appPath) {
|
||||
throw new Error('ManifestExtractFromFileServer not initialized. Call init(appPath) first.');
|
||||
throw new Error(
|
||||
'ManifestExtractFromFileServer not initialized. Call init(appPath) first.',
|
||||
);
|
||||
}
|
||||
|
||||
const { entryProperty } = options;
|
||||
const server = await this.getServer();
|
||||
const module = (await server.ssrLoadModule(filepath)) as Record<string, unknown>;
|
||||
const module = (await server.ssrLoadModule(filepath)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
const config = this.extractConfigFromModule<Record<string, unknown>>(module, entryProperty);
|
||||
const config = this.extractConfigFromModule<Record<string, unknown>>(
|
||||
module,
|
||||
entryProperty,
|
||||
);
|
||||
|
||||
if (!config) {
|
||||
const expectedExport = entryProperty
|
||||
@@ -48,11 +56,14 @@ export class ManifestExtractFromFileServer {
|
||||
const entryName = entryFunction.name;
|
||||
|
||||
if (!entryName) {
|
||||
throw new Error(`${entryProperty} function in ${filepath} must be a named function`);
|
||||
throw new Error(
|
||||
`${entryProperty} function in ${filepath} must be a named function`,
|
||||
);
|
||||
}
|
||||
|
||||
const importSource = await this.resolveEntryPath(filepath, entryName);
|
||||
const entryPath = importSource ?? path.relative(this.appPath, filepath).replace(/\\/g, '/');
|
||||
const entryPath =
|
||||
importSource ?? path.relative(this.appPath, filepath).replace(/\\/g, '/');
|
||||
|
||||
const { [entryProperty]: _, ...configWithoutEntry } = config;
|
||||
|
||||
@@ -72,7 +83,9 @@ export class ManifestExtractFromFileServer {
|
||||
|
||||
private async getServer(): Promise<ViteDevServer> {
|
||||
if (!this.appPath) {
|
||||
throw new Error('ManifestExtractFromFileServer not initialized. Call init(appPath) first.');
|
||||
throw new Error(
|
||||
'ManifestExtractFromFileServer not initialized. Call init(appPath) first.',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.server) {
|
||||
@@ -100,7 +113,10 @@ export class ManifestExtractFromFileServer {
|
||||
isPlainObject(value) &&
|
||||
typeof (value as Record<string, unknown>)[entryProperty!] === 'function';
|
||||
|
||||
if (isDefined(module.default) && (!entryProperty || hasValidEntry(module.default))) {
|
||||
if (
|
||||
isDefined(module.default) &&
|
||||
(!entryProperty || hasValidEntry(module.default))
|
||||
) {
|
||||
return module.default as T;
|
||||
}
|
||||
|
||||
@@ -124,7 +140,9 @@ export class ManifestExtractFromFileServer {
|
||||
const source = await fs.readFile(filepath, 'utf8');
|
||||
|
||||
const patterns = [
|
||||
new RegExp(`import\\s*\\{[^}]*\\b${entryName}\\b[^}]*\\}\\s*from\\s*['"]([^'"]+)['"]`),
|
||||
new RegExp(
|
||||
`import\\s*\\{[^}]*\\b${entryName}\\b[^}]*\\}\\s*from\\s*['"]([^'"]+)['"]`,
|
||||
),
|
||||
new RegExp(`import\\s+${entryName}\\s+from\\s*['"]([^'"]+)['"]`),
|
||||
];
|
||||
|
||||
@@ -142,19 +160,28 @@ export class ManifestExtractFromFileServer {
|
||||
}
|
||||
|
||||
const server = await this.getServer();
|
||||
const resolved = await server.pluginContainer.resolveId(importSpecifier, filepath);
|
||||
const resolved = await server.pluginContainer.resolveId(
|
||||
importSpecifier,
|
||||
filepath,
|
||||
);
|
||||
if (resolved?.id) {
|
||||
return path.relative(this.appPath, resolved.id).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
if (importSpecifier.startsWith('.')) {
|
||||
const absolutePath = path.resolve(path.dirname(filepath), importSpecifier);
|
||||
const absolutePath = path.resolve(
|
||||
path.dirname(filepath),
|
||||
importSpecifier,
|
||||
);
|
||||
const relativePath = path.relative(this.appPath, absolutePath);
|
||||
return (relativePath.endsWith('.ts') ? relativePath : `${relativePath}.ts`).replace(/\\/g, '/');
|
||||
return (
|
||||
relativePath.endsWith('.ts') ? relativePath : `${relativePath}.ts`
|
||||
).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const manifestExtractFromFileServer = new ManifestExtractFromFileServer();
|
||||
export const manifestExtractFromFileServer =
|
||||
new ManifestExtractFromFileServer();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { applicationEntityBuilder } from './entities/application';
|
||||
import {
|
||||
type EntityIdWithLocation,
|
||||
type ManifestWithoutSources,
|
||||
} from './entities/entity.interface';
|
||||
} from '@/cli/utilities/build/manifest/entities/entity-interface';
|
||||
import { frontComponentEntityBuilder } from './entities/front-component';
|
||||
import { functionEntityBuilder } from './entities/function';
|
||||
import { objectEntityBuilder } from './entities/object';
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type ValidationError,
|
||||
type ValidationResult,
|
||||
type ValidationWarning,
|
||||
} from './manifest.types';
|
||||
} from '@/cli/utilities/build/manifest/manifest-types';
|
||||
|
||||
const collectAllDuplicates = (
|
||||
manifest: ManifestWithoutSources,
|
||||
@@ -39,7 +39,10 @@ export const validateManifest = (
|
||||
errors,
|
||||
);
|
||||
objectEntityBuilder.validate(manifest.objects ?? [], errors);
|
||||
objectExtensionEntityBuilder.validate(manifest.objectExtensions ?? [], errors);
|
||||
objectExtensionEntityBuilder.validate(
|
||||
manifest.objectExtensions ?? [],
|
||||
errors,
|
||||
);
|
||||
functionEntityBuilder.validate(manifest.functions ?? [], errors);
|
||||
roleEntityBuilder.validate(manifest.roles ?? [], errors);
|
||||
frontComponentEntityBuilder.validate(manifest.frontComponents ?? [], errors);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import chokidar, { type FSWatcher } from 'chokidar';
|
||||
import path from 'path';
|
||||
import { createLogger } from '../common/logger';
|
||||
import { runManifestBuild, type ManifestBuildResult } from './manifest-build';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import {
|
||||
type ManifestBuildResult,
|
||||
runManifestBuild,
|
||||
} from '@/cli/utilities/build/manifest/manifest-build';
|
||||
|
||||
const logger = createLogger('manifest-watch');
|
||||
|
||||
@@ -30,7 +33,8 @@ export class ManifestWatcher {
|
||||
'**/node_modules/**',
|
||||
'**/.twenty/**',
|
||||
'**/dist/**',
|
||||
(filePath: string) => filePath.includes('/.twenty/') || filePath.includes('\\.twenty\\'),
|
||||
(filePath: string) =>
|
||||
filePath.includes('/.twenty/') || filePath.includes('\\.twenty\\'),
|
||||
],
|
||||
ignoreInitial: true,
|
||||
awaitWriteFinish: {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
|
||||
import { OUTPUT_DIR } from '../common/constants';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
|
||||
export const writeManifestToOutput = async (
|
||||
appPath: string,
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import { ApiService } from '@/cli/utilities/api/services/api.service';
|
||||
import { ConfigService } from '@/cli/utilities/config/services/config.service';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { generate } from '@genql/cli';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
|
||||
export const GENERATED_FOLDER_NAME = 'generated';
|
||||
|
||||
export class GenerateService {
|
||||
export class ClientService {
|
||||
private configService: ConfigService;
|
||||
private apiService: ApiService;
|
||||
|
||||
@@ -20,7 +20,7 @@ export class GenerateService {
|
||||
this.apiService = new ApiService();
|
||||
}
|
||||
|
||||
async generateClient(appPath: string): Promise<void> {
|
||||
async generate(appPath: string): Promise<void> {
|
||||
const outputPath = join(appPath, GENERATED_FOLDER_NAME);
|
||||
|
||||
console.log(chalk.blue('📦 Generating Twenty client...'));
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { convertToLabel } from '@/cli/utilities/entity/utils/entity-label';
|
||||
import { convertToLabel } from '@/cli/utilities/entity/entity-label';
|
||||
|
||||
describe('convertToLabel', () => {
|
||||
it('should convert to label', () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getFrontComponentBaseFile } from '@/cli/utilities/entity/utils/entity-front-component-template';
|
||||
import { getFrontComponentBaseFile } from '@/cli/utilities/entity/entity-front-component-template';
|
||||
|
||||
describe('getFrontComponentBaseFile', () => {
|
||||
it('should render proper file using defineFrontComponent', () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getFunctionBaseFile } from '@/cli/utilities/entity/utils/entity-function-template';
|
||||
import { getFunctionBaseFile } from '@/cli/utilities/entity/entity-function-template';
|
||||
|
||||
describe('getFunctionBaseFile', () => {
|
||||
it('should render proper file using defineFunction', () => {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getNewObjectFileContent } from '@/cli/utilities/entity/utils/entity-object-template';
|
||||
import { getNewObjectFileContent } from '@/cli/utilities/entity/entity-object-template';
|
||||
|
||||
describe('getNewObjectFileContent', () => {
|
||||
it('should return proper object file using defineObject', () => {
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
import { getRoleBaseFile } from '@/cli/utilities/entity/utils/entity-role-template';
|
||||
import { getRoleBaseFile } from '@/cli/utilities/entity/entity-role-template';
|
||||
|
||||
describe('getRoleBaseFile', () => {
|
||||
it('should render proper file using defineRole', () => {
|
||||
@@ -11,11 +11,9 @@ describe('getRoleBaseFile', () => {
|
||||
expect(result).toContain('export default defineRole({');
|
||||
|
||||
expect(result).toContain(
|
||||
"universalIdentifier: MY_ROLE_ROLE_UNIVERSAL_IDENTIFIER",
|
||||
);
|
||||
expect(result).toContain(
|
||||
"'71e45a58-41da-4ae4-8b73-a543c0a9d3d4'",
|
||||
'universalIdentifier: MY_ROLE_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
);
|
||||
expect(result).toContain("'71e45a58-41da-4ae4-8b73-a543c0a9d3d4'");
|
||||
expect(result).toContain("label: 'my-role'");
|
||||
expect(result).toContain("description: 'Add a description for your role'");
|
||||
|
||||
@@ -48,7 +46,9 @@ describe('getRoleBaseFile', () => {
|
||||
name: 'admin-access',
|
||||
});
|
||||
|
||||
expect(result).toContain('export const ADMIN_ACCESS_ROLE_UNIVERSAL_IDENTIFIER');
|
||||
expect(result).toContain(
|
||||
'export const ADMIN_ACCESS_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
);
|
||||
expect(result).toContain(
|
||||
'universalIdentifier: ADMIN_ACCESS_ROLE_UNIVERSAL_IDENTIFIER',
|
||||
);
|
||||
-13
@@ -43,10 +43,6 @@ export const parseJsoncString = (
|
||||
return result;
|
||||
};
|
||||
|
||||
export const parseTextFile = async (filePath: string) => {
|
||||
return await fs.readFile(filePath, 'utf8');
|
||||
};
|
||||
|
||||
export const parseJsoncFile = async (
|
||||
filePath: string,
|
||||
options: JsoncParseOptions = {},
|
||||
@@ -61,12 +57,3 @@ export const parseJsoncFile = async (
|
||||
throw new Error(`Failed to read file ${filePath}: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const writeJsoncFile = async (
|
||||
filePath: string,
|
||||
data: any,
|
||||
options: { spaces?: number } = {},
|
||||
): Promise<void> => {
|
||||
const content = JSON.stringify(data, null, options.spaces ?? 2);
|
||||
await fs.writeFile(filePath, content, 'utf8');
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { join } from 'path';
|
||||
|
||||
export const formatPath = (appPath?: string) => {
|
||||
return appPath && !appPath?.startsWith('/')
|
||||
? join(CURRENT_EXECUTION_DIRECTORY, appPath)
|
||||
: appPath;
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import path from 'path';
|
||||
import { OUTPUT_DIR } from '@/cli/utilities/build/common/constants';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import { type ApplicationManifest } from 'twenty-shared/application';
|
||||
|
||||
export class FileUploader {
|
||||
private apiService = new ApiService();
|
||||
private applicationUniversalIdentifier: string;
|
||||
private appPath: string;
|
||||
private logger = createLogger('file-upload');
|
||||
|
||||
constructor(options: {
|
||||
applicationUniversalIdentifier: string;
|
||||
appPath: string;
|
||||
}) {
|
||||
this.applicationUniversalIdentifier =
|
||||
options.applicationUniversalIdentifier;
|
||||
this.appPath = options.appPath;
|
||||
}
|
||||
|
||||
async uploadFile({
|
||||
builtPath,
|
||||
fileFolder,
|
||||
}: {
|
||||
builtPath: string;
|
||||
fileFolder: FileFolder;
|
||||
}) {
|
||||
const uploadResult = await this.apiService.uploadFile({
|
||||
filePath: path.join(this.appPath, OUTPUT_DIR, builtPath),
|
||||
builtHandlerPath: builtPath,
|
||||
fileFolder,
|
||||
applicationUniversalIdentifier: this.applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
if (uploadResult.success) {
|
||||
this.logger.success(`☁️ Uploaded ${builtPath}`);
|
||||
} else {
|
||||
this.logger.error(
|
||||
`Failed to upload ${builtPath} -- ${uploadResult.error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async uploadManifestBuiltFiles(manifest: ApplicationManifest) {
|
||||
const uploadPromises = [
|
||||
...manifest.functions.map((builtFile) =>
|
||||
this.uploadFile({
|
||||
builtPath: builtFile.builtHandlerPath,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
}),
|
||||
),
|
||||
...manifest.frontComponents.map((builtFile) =>
|
||||
this.uploadFile({
|
||||
builtPath: builtFile.builtComponentPath,
|
||||
fileFolder: FileFolder.BuiltFrontComponent,
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
await Promise.all(uploadPromises);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import dotenv from 'dotenv';
|
||||
import { findPathFile } from './file-find';
|
||||
|
||||
export const loadEnvVariables = async (appPath: string) => {
|
||||
let envFile = '';
|
||||
|
||||
try {
|
||||
const envFilePath = await findPathFile(appPath, '.env');
|
||||
|
||||
envFile = await fs.readFile(envFilePath, 'utf8');
|
||||
} catch {
|
||||
// Allow missing .env
|
||||
}
|
||||
|
||||
return dotenv.parse(envFile);
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/constants/current-execution-directory';
|
||||
import { join, posix, relative, sep } from 'path';
|
||||
|
||||
export const formatPath = (appPath?: string) => {
|
||||
return appPath && !appPath?.startsWith('/')
|
||||
? join(CURRENT_EXECUTION_DIRECTORY, appPath)
|
||||
: appPath;
|
||||
};
|
||||
|
||||
export const toPosixRelative = (filepath: string, basePath: string): string => {
|
||||
const rel = relative(basePath, filepath);
|
||||
return rel.split(sep).join(posix.sep);
|
||||
};
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
type Diagnostic,
|
||||
formatDiagnosticsWithColorAndContext,
|
||||
sys,
|
||||
type Diagnostic,
|
||||
formatDiagnosticsWithColorAndContext,
|
||||
sys,
|
||||
} from 'typescript';
|
||||
|
||||
export const formatAndWarnTsDiagnostics = ({
|
||||
@@ -17,5 +17,6 @@ export default defineConfig({
|
||||
diff: {
|
||||
truncateThreshold: 0,
|
||||
},
|
||||
fileParallelism: false,
|
||||
},
|
||||
});
|
||||
|
||||
+1
-1
@@ -6,6 +6,7 @@ import {
|
||||
type CompositeProperty,
|
||||
type CompositeType,
|
||||
} from 'twenty-shared/types';
|
||||
import { pascalCase } from 'twenty-shared/utils';
|
||||
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util';
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
type FieldMetadataDefaultOption,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
export class CompositeFieldMetadataGqlEnumTypeGenerator {
|
||||
|
||||
+1
-2
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GraphQLEnumType } from 'graphql';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDefined, pascalCase } from 'twenty-shared/utils';
|
||||
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
|
||||
@@ -13,7 +13,6 @@ import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metad
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { transformEnumValue } from 'src/engine/utils/transform-enum-value';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
export class EnumFieldMetadataGqlEnumTypeGenerator {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user