diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields.tsx
index ed90400e8c..323675355f 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields.tsx
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields.tsx
@@ -10,8 +10,8 @@ import { getWorkflowCodeFieldsEnumSelectOptions } from '@/workflow/workflow-step
import { getWorkflowCodeFieldsLeafKind } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsLeafKind';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
-import { isNonEmptyArray, isNonEmptyString, isObject } from '@sniptt/guards';
-import { isDefined } from 'twenty-shared/utils';
+import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
+import { isDefined, isPlainObject } from 'twenty-shared/utils';
import { type FunctionInput, type InputSchema } from 'twenty-shared/workflow';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -59,7 +59,7 @@ export const WorkflowEditActionCodeFields = ({
? schemaProperty.label
: inputKey;
- if (inputValue !== null && isObject(inputValue)) {
+ if (isPlainObject(inputValue)) {
return (
{displayLabel}
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/__tests__/mergeDefaultFunctionInputAndFunctionInput.test.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/__tests__/mergeDefaultFunctionInputAndFunctionInput.test.ts
index a3720eb539..233315172d 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/__tests__/mergeDefaultFunctionInputAndFunctionInput.test.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/__tests__/mergeDefaultFunctionInputAndFunctionInput.test.ts
@@ -24,4 +24,28 @@ describe('mergeDefaultFunctionInputAndFunctionInput', () => {
}),
).toEqual(expectedResult);
});
+
+ it('should preserve array values without recursing into them as nested objects', () => {
+ const newInput = { briefs: [], b: null };
+ const oldInput = { briefs: [], b: 5 };
+
+ expect(
+ mergeDefaultFunctionInputAndFunctionInput({
+ newInput,
+ oldInput,
+ }),
+ ).toEqual({ briefs: [], b: 5 });
+ });
+
+ it('should keep an array typed by the user instead of resetting it', () => {
+ const newInput = { briefs: [], b: null };
+ const oldInput = { briefs: '["a", "b"]', b: null };
+
+ expect(
+ mergeDefaultFunctionInputAndFunctionInput({
+ newInput,
+ oldInput,
+ }),
+ ).toEqual({ briefs: '["a", "b"]', b: null });
+ });
});
diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput.ts b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput.ts
index 2f0d3634d2..da97586419 100644
--- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput.ts
+++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/utils/mergeDefaultFunctionInputAndFunctionInput.ts
@@ -1,5 +1,5 @@
+import { isPlainObject } from 'twenty-shared/utils';
import { type FunctionInput } from 'twenty-shared/workflow';
-import { isObject } from '@sniptt/guards';
export const mergeDefaultFunctionInputAndFunctionInput = ({
newInput,
@@ -16,12 +16,12 @@ export const mergeDefaultFunctionInputAndFunctionInput = ({
if (!(key in oldInput)) {
result[key] = newValue;
- } else if (newValue === null && isObject(oldValue)) {
+ } else if (newValue === null && isPlainObject(oldValue)) {
result[key] = null;
- } else if (isObject(newValue)) {
+ } else if (isPlainObject(newValue)) {
result[key] = mergeDefaultFunctionInputAndFunctionInput({
newInput: newValue,
- oldInput: isObject(oldValue) ? oldValue : {},
+ oldInput: isPlainObject(oldValue) ? oldValue : {},
});
} else {
result[key] = oldValue;
diff --git a/packages/twenty-shared/src/logic-function/__tests__/get-function-input-schema.test.ts b/packages/twenty-shared/src/logic-function/__tests__/get-function-input-schema.test.ts
index 79e411bba0..8a62274886 100644
--- a/packages/twenty-shared/src/logic-function/__tests__/get-function-input-schema.test.ts
+++ b/packages/twenty-shared/src/logic-function/__tests__/get-function-input-schema.test.ts
@@ -26,6 +26,66 @@ describe('getFunctionInputSchema', () => {
expect(result).toEqual([{ type: 'string' }, { type: 'number' }]);
});
+ it('should parse any[] as an array with unknown items', () => {
+ const fileContent = `
+ export const main = (params: { briefs: any[] }): void => {
+ return;
+ };
+ `;
+ const result = getFunctionInputSchema(fileContent);
+
+ expect(result).toEqual([
+ {
+ type: 'object',
+ properties: {
+ briefs: { type: 'array', items: {} },
+ },
+ },
+ ]);
+ });
+
+ it('should parse Array
generic syntax the same way as T[]', () => {
+ const fileContent = `
+ export const main = (params: {
+ briefs: Array;
+ names: Array;
+ readonlyNames: ReadonlyArray;
+ }): void => {
+ return;
+ };
+ `;
+ const result = getFunctionInputSchema(fileContent);
+
+ expect(result).toEqual([
+ {
+ type: 'object',
+ properties: {
+ briefs: { type: 'array', items: {} },
+ names: { type: 'array', items: { type: 'string' } },
+ readonlyNames: { type: 'array', items: { type: 'string' } },
+ },
+ },
+ ]);
+ });
+
+ it('should fall back to an unknown type for unrecognized type references', () => {
+ const fileContent = `
+ export const main = (params: { value: Map }): void => {
+ return;
+ };
+ `;
+ const result = getFunctionInputSchema(fileContent);
+
+ expect(result).toEqual([
+ {
+ type: 'object',
+ properties: {
+ value: {},
+ },
+ },
+ ]);
+ });
+
it('should analyze a complex function correctly', () => {
const fileContent = `
function testFunction(
diff --git a/packages/twenty-shared/src/logic-function/get-function-input-schema.ts b/packages/twenty-shared/src/logic-function/get-function-input-schema.ts
index 215be75a1b..4da182aaf8 100644
--- a/packages/twenty-shared/src/logic-function/get-function-input-schema.ts
+++ b/packages/twenty-shared/src/logic-function/get-function-input-schema.ts
@@ -4,6 +4,7 @@ import {
createSourceFile,
type FunctionDeclaration,
type FunctionLikeDeclaration,
+ type Identifier,
type LiteralTypeNode,
type Node,
type PropertySignature,
@@ -11,6 +12,7 @@ import {
type StringLiteral,
SyntaxKind,
type TypeNode,
+ type TypeReferenceNode,
type UnionTypeNode,
type VariableStatement,
} from 'typescript';
@@ -31,6 +33,24 @@ const getTypeString = (typeNode: TypeNode): InputJsonSchema => {
type: 'array',
items: getTypeString((typeNode as ArrayTypeNode).elementType),
};
+ case SyntaxKind.TypeReference: {
+ const typeReferenceNode = typeNode as TypeReferenceNode;
+ const typeName =
+ typeReferenceNode.typeName.kind === SyntaxKind.Identifier
+ ? (typeReferenceNode.typeName as Identifier).text
+ : undefined;
+
+ if (typeName === 'Array' || typeName === 'ReadonlyArray') {
+ const elementType = typeReferenceNode.typeArguments?.[0];
+
+ return {
+ type: 'array',
+ items: isDefined(elementType) ? getTypeString(elementType) : {},
+ };
+ }
+
+ return {};
+ }
case SyntaxKind.ObjectKeyword:
return { type: 'object' };
case SyntaxKind.TypeLiteral: {
diff --git a/packages/twenty-shared/src/workflow/utils/__tests__/getDefaultFunctionInputFromInputSchema.test.ts b/packages/twenty-shared/src/workflow/utils/__tests__/getDefaultFunctionInputFromInputSchema.test.ts
index 19bd5ea1f2..51931b74c2 100644
--- a/packages/twenty-shared/src/workflow/utils/__tests__/getDefaultFunctionInputFromInputSchema.test.ts
+++ b/packages/twenty-shared/src/workflow/utils/__tests__/getDefaultFunctionInputFromInputSchema.test.ts
@@ -1,3 +1,4 @@
+import { type InputJsonSchema } from '@/logic-function';
import { getFunctionInputFromInputSchema, type InputSchema } from '@/workflow';
describe('getDefaultFunctionInputFromInputSchema', () => {
@@ -40,4 +41,19 @@ describe('getDefaultFunctionInputFromInputSchema', () => {
expectedResult,
);
});
+
+ it('should init arrays with unknown items (e.g. any[]) as empty arrays', () => {
+ const inputSchema: InputJsonSchema[] = [
+ {
+ type: 'object',
+ properties: {
+ briefs: { type: 'array', items: {} },
+ },
+ },
+ ];
+
+ expect(getFunctionInputFromInputSchema(inputSchema)).toEqual([
+ { briefs: [] },
+ ]);
+ });
});