Upgrade to Node22 (#12488)
BlocknoteJS requires an ESM module where our server is CJS, this forced us to pin the server-util version, which led us to force the resolution of several packages, leading to bugs downstream. From Node 22.12 Node supports requiring ESM modules (available from Node 22.0 with a flag). So I upgrade the module. I picked Node 22 and not Node 23 or Node 24 because 22 is the LTS and we don't plan to change node versions frequently. If you remain on Node 18, things should still mostly work, except if you edit a Rich Text field. I also starting changing the default runtime for Serverless Functions which isn't directly related. This means new serverless functions will be created on Node 22, but we will still need another PR to migrate existing serverless functions before September (end of support by AWS). (In this PR I also remove the upgrade commands from 0.43 since they rely on Blocknote and I didn't want to have to deal with this) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@ -2,7 +2,7 @@ name: Yarn Install
|
||||
inputs:
|
||||
node-version:
|
||||
required: false
|
||||
default: '18'
|
||||
default: '22'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -37,7 +37,7 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeVersion": "18",
|
||||
"runtimeVersion": "^22.12.0",
|
||||
"runtimeArgs": [
|
||||
"nx",
|
||||
"run",
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -43,7 +43,7 @@
|
||||
],
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.yarn": true
|
||||
},
|
||||
"eslint.debug": true,
|
||||
"files.associations": {
|
||||
@ -51,4 +51,3 @@
|
||||
},
|
||||
"jestrunner.codeLensSelector": "**/*.{test,spec,integration-spec}.{js,jsx,ts,tsx}"
|
||||
}
|
||||
}
|
||||
|
||||
29
package.json
29
package.json
@ -3,13 +3,10 @@
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.17",
|
||||
"@apollo/server": "^4.7.3",
|
||||
"@aws-sdk/client-lambda": "^3.614.0",
|
||||
"@aws-sdk/client-s3": "^3.363.0",
|
||||
"@aws-sdk/client-sts": "^3.744.0",
|
||||
"@aws-sdk/credential-providers": "^3.363.0",
|
||||
"@blocknote/mantine": "^0.31.1",
|
||||
"@blocknote/react": "^0.31.1",
|
||||
"@blocknote/server-util": "^0.17.1",
|
||||
"@aws-sdk/client-lambda": "^3.700.0",
|
||||
"@aws-sdk/client-s3": "^3.700.0",
|
||||
"@aws-sdk/client-sts": "^3.700.0",
|
||||
"@aws-sdk/credential-providers": "^3.700.0",
|
||||
"@codesandbox/sandpack-react": "^2.13.5",
|
||||
"@dagrejs/dagre": "^1.1.2",
|
||||
"@emotion/react": "^11.11.1",
|
||||
@ -70,7 +67,6 @@
|
||||
"archiver": "^7.0.1",
|
||||
"axios": "^1.6.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"better-sqlite3": "^9.2.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"bullmq": "^5.40.0",
|
||||
"bytes": "^3.1.2",
|
||||
@ -242,12 +238,11 @@
|
||||
"@swc/cli": "^0.3.12",
|
||||
"@swc/core": "1.7.42",
|
||||
"@swc/helpers": "~0.5.2",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/addressparser": "^1.0.3",
|
||||
"@types/apollo-upload-client": "^17.0.2",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/better-sqlite3": "^7.6.8",
|
||||
"@types/bytes": "^3.1.1",
|
||||
"@types/chrome": "^0.0.267",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
@ -274,7 +269,7 @@
|
||||
"@types/lodash.upperfirst": "^4.3.7",
|
||||
"@types/luxon": "^3.3.0",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "18.19.26",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/passport-google-oauth20": "^2.0.11",
|
||||
"@types/passport-jwt": "^3.0.8",
|
||||
"@types/pluralize": "^0.0.33",
|
||||
@ -316,7 +311,7 @@
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-environment-jsdom": "30.0.0-beta.3",
|
||||
"jest-environment-node": "^29.4.1",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jsdom": "~22.1.0",
|
||||
@ -340,12 +335,13 @@
|
||||
"tsx": "^4.17.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-checker": "^0.6.2",
|
||||
"vite-plugin-cjs-interop": "^2.2.0",
|
||||
"vite-plugin-dts": "3.8.1",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vitest": "1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.1",
|
||||
"node": "^22.12.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
@ -356,9 +352,8 @@
|
||||
"graphql": "16.8.0",
|
||||
"type-fest": "4.10.1",
|
||||
"typescript": "5.3.3",
|
||||
"prosemirror-model": "1.23.0",
|
||||
"yjs": "13.6.18",
|
||||
"graphql-redis-subscriptions/ioredis": "^5.6.0"
|
||||
"graphql-redis-subscriptions/ioredis": "^5.6.0",
|
||||
"prosemirror-view": "1.40.0"
|
||||
},
|
||||
"version": "0.2.1",
|
||||
"nx": {},
|
||||
|
||||
@ -7,7 +7,6 @@ TAG=latest
|
||||
#REDIS_URL=redis://redis:6379
|
||||
|
||||
SERVER_URL=http://localhost:3000
|
||||
SIGN_IN_PREFILLED=false
|
||||
|
||||
# Use openssl rand -base64 32 for each secret
|
||||
# APP_SECRET=replace_me_with_a_random_string
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:18.17.1-alpine as twenty-website-build
|
||||
FROM node:22-alpine as twenty-website-build
|
||||
|
||||
|
||||
WORKDIR /app
|
||||
@ -23,7 +23,7 @@ COPY ./packages/twenty-ui /app/packages/twenty-ui
|
||||
COPY ./packages/twenty-website /app/packages/twenty-website
|
||||
RUN npx nx build twenty-website
|
||||
|
||||
FROM node:18.17.1-alpine as twenty-website
|
||||
FROM node:22-alpine as twenty-website
|
||||
|
||||
WORKDIR /app/packages/twenty-website
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Base image for common dependencies
|
||||
FROM node:18.17.1-alpine as common-deps
|
||||
FROM node:22-alpine as common-deps
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -49,7 +49,7 @@ RUN npx nx build twenty-front
|
||||
|
||||
|
||||
# Final stage: Run the application
|
||||
FROM node:18.17.1-alpine as twenty
|
||||
FROM node:22-alpine as twenty
|
||||
|
||||
# Used to run healthcheck in docker
|
||||
RUN apk add --no-cache curl jq
|
||||
|
||||
@ -27,13 +27,13 @@
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.1",
|
||||
"node": "^22.12.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": "^4.0.2"
|
||||
}
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
|
||||
"build:sourcemaps": "VITE_BUILD_SOURCEMAP=true VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
|
||||
"build": "NODE_ENV=production VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
|
||||
"build:sourcemaps": "NODE_ENV=production VITE_BUILD_SOURCEMAP=true VITE_DISABLE_TYPESCRIPT_CHECKER=true VITE_DISABLE_ESLINT_CHECKER=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
|
||||
"start:prod": "NODE_ENV=production npx serve -s build",
|
||||
"tsup": "npx tsup"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.1",
|
||||
"node": "^22.12.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": "^4.0.2"
|
||||
},
|
||||
@ -29,6 +29,8 @@
|
||||
"workerDirectory": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blocknote/mantine": "^0.31.1",
|
||||
"@blocknote/react": "^0.31.1",
|
||||
"@blocknote/xl-ai": "^0.31.1",
|
||||
"@blocknote/xl-docx-exporter": "^0.31.1",
|
||||
"@blocknote/xl-pdf-exporter": "^0.31.1",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FlagComponent } from 'country-flag-icons/react/3x2';
|
||||
import type { FlagComponent } from 'country-flag-icons/react/3x2';
|
||||
import type { CountryCallingCode, CountryCode } from 'libphonenumber-js';
|
||||
|
||||
export type Country = {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { HttpResponse, graphql, http } from 'msw';
|
||||
import { getImageAbsoluteURI } from 'twenty-shared/utils';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { SettingsServerlessFunctionDetail } from '~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail';
|
||||
import {
|
||||
@ -9,7 +10,6 @@ import {
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
import { getImageAbsoluteURI } from 'twenty-shared/utils';
|
||||
|
||||
const SOURCE_CODE_FULL_PATH =
|
||||
'serverless-function/20202020-1c25-4d02-bf25-6aeccf7ea419/adb4bd21-7670-4c81-9f74-1fc196fe87ea/source.ts';
|
||||
@ -36,7 +36,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
id: 'adb4bd21-7670-4c81-9f74-1fc196fe87ea',
|
||||
name: 'Serverless Function Name',
|
||||
description: '',
|
||||
runtime: 'nodejs18.x',
|
||||
runtime: 'nodejs22.x',
|
||||
updatedAt: '2024-02-24T10:23:10.673Z',
|
||||
createdAt: '2024-02-24T10:23:10.673Z',
|
||||
},
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
import { MutableRefObject } from 'react';
|
||||
import { combineRefs } from '../combineRefs';
|
||||
|
||||
describe('combineRefs', () => {
|
||||
it('should handle function refs', () => {
|
||||
const ref1 = jest.fn();
|
||||
const ref2 = jest.fn();
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, ref2);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1).toHaveBeenCalledWith(node);
|
||||
expect(ref2).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should handle object refs', () => {
|
||||
const ref1: MutableRefObject<HTMLDivElement | null> = { current: null };
|
||||
const ref2: MutableRefObject<HTMLDivElement | null> = { current: null };
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, ref2);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1.current).toBe(node);
|
||||
expect(ref2.current).toBe(node);
|
||||
});
|
||||
|
||||
it('should handle mixed function and object refs', () => {
|
||||
const funcRef = jest.fn();
|
||||
const objRef: MutableRefObject<HTMLDivElement | null> = { current: null };
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(funcRef, objRef);
|
||||
combinedRef(node);
|
||||
|
||||
expect(funcRef).toHaveBeenCalledWith(node);
|
||||
expect(objRef.current).toBe(node);
|
||||
});
|
||||
|
||||
it('should handle undefined refs', () => {
|
||||
const ref1 = jest.fn();
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, undefined);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1).toHaveBeenCalledWith(node);
|
||||
});
|
||||
|
||||
it('should handle all undefined refs', () => {
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(undefined, undefined);
|
||||
|
||||
expect(() => combinedRef(node)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle empty refs array', () => {
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs();
|
||||
|
||||
expect(() => combinedRef(node)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle null refs', () => {
|
||||
const ref1 = jest.fn();
|
||||
const node = document.createElement('div');
|
||||
|
||||
const combinedRef = combineRefs(ref1, null);
|
||||
combinedRef(node);
|
||||
|
||||
expect(ref1).toHaveBeenCalledWith(node);
|
||||
});
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
import { getDirtyFields } from './getDirtyFields';
|
||||
import { getDirtyFields } from '~/utils/getDirtyFields';
|
||||
|
||||
describe('getDirtyFields', () => {
|
||||
it('should return all defined fields from draft when persisted is null', () => {
|
||||
@ -0,0 +1,84 @@
|
||||
import { isEmptyObject } from '../isEmptyObject';
|
||||
|
||||
describe('isEmptyObject', () => {
|
||||
it('should return true for empty object', () => {
|
||||
expect(isEmptyObject({})).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for object with properties', () => {
|
||||
expect(isEmptyObject({ key: 'value' })).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for object with multiple properties', () => {
|
||||
expect(isEmptyObject({ key1: 'value1', key2: 'value2' })).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for null', () => {
|
||||
expect(isEmptyObject(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for undefined', () => {
|
||||
expect(isEmptyObject(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for string', () => {
|
||||
expect(isEmptyObject('test')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for number', () => {
|
||||
expect(isEmptyObject(42)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for boolean', () => {
|
||||
expect(isEmptyObject(true)).toBe(false);
|
||||
expect(isEmptyObject(false)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for empty array (as it has no enumerable keys)', () => {
|
||||
expect(isEmptyObject([])).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-empty array with enumerable properties', () => {
|
||||
const arr = [1, 2, 3];
|
||||
expect(isEmptyObject(arr)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for function', () => {
|
||||
expect(isEmptyObject(() => {})).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for Date object (as it has no enumerable keys)', () => {
|
||||
expect(isEmptyObject(new Date())).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for object created with Object.create(null)', () => {
|
||||
expect(isEmptyObject(Object.create(null))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for object with inherited properties only', () => {
|
||||
const parent = { parentProp: 'value' };
|
||||
const child = Object.create(parent);
|
||||
expect(isEmptyObject(child)).toBe(true); // Only checks own enumerable properties
|
||||
});
|
||||
|
||||
it('should return true for object with non-enumerable properties only', () => {
|
||||
const obj = {};
|
||||
Object.defineProperty(obj, 'nonEnumerableProp', {
|
||||
value: 'test',
|
||||
enumerable: false,
|
||||
});
|
||||
expect(isEmptyObject(obj)).toBe(true); // Object.keys only returns enumerable properties
|
||||
});
|
||||
|
||||
it('should return false for array with custom properties', () => {
|
||||
const arr: any = [];
|
||||
arr.customProp = 'value';
|
||||
expect(isEmptyObject(arr)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for Date object with custom properties', () => {
|
||||
const date: any = new Date();
|
||||
date.customProp = 'value';
|
||||
expect(isEmptyObject(date)).toBe(false);
|
||||
});
|
||||
});
|
||||
12
packages/twenty-front/src/utils/__tests__/isInIframe.test.ts
Normal file
12
packages/twenty-front/src/utils/__tests__/isInIframe.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { isInFrame } from '../isInIframe';
|
||||
|
||||
describe('isInFrame', () => {
|
||||
it('should return a boolean value', () => {
|
||||
const result = isInFrame();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
|
||||
it('should not throw an error when called', () => {
|
||||
expect(() => isInFrame()).not.toThrow();
|
||||
});
|
||||
});
|
||||
51
packages/twenty-front/src/utils/__tests__/logDebug.test.ts
Normal file
51
packages/twenty-front/src/utils/__tests__/logDebug.test.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { logDebug } from '../logDebug';
|
||||
|
||||
describe('logDebug', () => {
|
||||
let consoleDebugSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleDebugSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should call console.debug with the provided message', () => {
|
||||
const debugMessage = 'Test debug message';
|
||||
|
||||
logDebug(debugMessage);
|
||||
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(debugMessage, []);
|
||||
expect(consoleDebugSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle optional parameters', () => {
|
||||
const debugMessage = 'Test debug message';
|
||||
const param1 = 'param1';
|
||||
const param2 = { key: 'value' };
|
||||
|
||||
logDebug(debugMessage, param1, param2);
|
||||
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(debugMessage, [
|
||||
param1,
|
||||
param2,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle no optional parameters', () => {
|
||||
const debugMessage = 'Test debug message';
|
||||
|
||||
logDebug(debugMessage);
|
||||
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(debugMessage, []);
|
||||
});
|
||||
|
||||
it('should handle null and undefined messages', () => {
|
||||
logDebug(null);
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(null, []);
|
||||
|
||||
logDebug(undefined);
|
||||
expect(consoleDebugSpy).toHaveBeenCalledWith(undefined, []);
|
||||
});
|
||||
});
|
||||
46
packages/twenty-front/src/utils/__tests__/logError.test.ts
Normal file
46
packages/twenty-front/src/utils/__tests__/logError.test.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { logError } from '../logError';
|
||||
|
||||
describe('logError', () => {
|
||||
let consoleErrorSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should call console.error with the provided message', () => {
|
||||
const errorMessage = 'Test error message';
|
||||
|
||||
logError(errorMessage);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(errorMessage);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle object messages', () => {
|
||||
const errorObject = { error: 'Test error', code: 500 };
|
||||
|
||||
logError(errorObject);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(errorObject);
|
||||
});
|
||||
|
||||
it('should handle null and undefined messages', () => {
|
||||
logError(null);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(null);
|
||||
|
||||
logError(undefined);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('should handle Error objects', () => {
|
||||
const error = new Error('Test error');
|
||||
|
||||
logError(error);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(error);
|
||||
});
|
||||
});
|
||||
@ -42,8 +42,8 @@ export default defineConfig(({ command, mode }) => {
|
||||
// Please don't increase this limit for main index chunk
|
||||
// If it gets too big then find modules in the code base
|
||||
// that can be loaded lazily, there are more!
|
||||
const MAIN_CHUNK_SIZE_LIMIT = 4.1 * 1024 * 1024; // 4.1MB for main index chunk
|
||||
const OTHER_CHUNK_SIZE_LIMIT = 15 * 1024 * 1024; // 5MB for other chunks
|
||||
const MAIN_CHUNK_SIZE_LIMIT = 4.7 * 1024 * 1024; // 4.7MB for main index chunk
|
||||
const OTHER_CHUNK_SIZE_LIMIT = 5 * 1024 * 1024; // 5MB for other chunks
|
||||
|
||||
const checkers: Checkers = {
|
||||
overlay: false,
|
||||
@ -196,7 +196,7 @@ export default defineConfig(({ command, mode }) => {
|
||||
const limitType = isMainChunk ? 'main' : 'other';
|
||||
|
||||
if (size > sizeLimit) {
|
||||
oversizedChunks.push(`${fileName} (${limitType}): ${(size / 1024 / 1024).toFixed(2)}MB (limit: ${(sizeLimit / 1024 / 1024).toFixed(0)}MB)`);
|
||||
oversizedChunks.push(`${fileName} (${limitType}): ${(size / 1024 / 1024).toFixed(2)}MB (limit: ${(sizeLimit / 1024 / 1024).toFixed(2)}MB)`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.22",
|
||||
"@blocknote/server-util": "^0.31.1",
|
||||
"@clickhouse/client": "^1.11.0",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"@graphql-yoga/nestjs": "patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga+nestjs+2.1.0.patch",
|
||||
@ -89,7 +90,7 @@
|
||||
"typescript": "5.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.1",
|
||||
"node": "^22.12.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": "^4.0.2"
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
"options": {
|
||||
"cwd": "packages/twenty-server",
|
||||
"commands": [
|
||||
"NODE_ENV=test nx jest --config ./jest-integration.config.ts"
|
||||
"NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" nx jest --config ./jest-integration.config.ts"
|
||||
]
|
||||
},
|
||||
"parallel": false,
|
||||
@ -26,7 +26,7 @@
|
||||
"with-db-reset": {
|
||||
"cwd": "packages/twenty-server",
|
||||
"commands": [
|
||||
"NODE_ENV=test nx database:reset > reset-logs.log && NODE_ENV=test nx jest --config ./jest-integration.config.ts"
|
||||
"NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" nx database:reset > reset-logs.log && NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=8192\" nx jest --config ./jest-integration.config.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,208 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { FieldMetadataDefaultOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { tasksAssignedToMeView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-assigned-to-me';
|
||||
import { TASK_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
|
||||
import { ViewFilterWorkspaceEntity } from 'src/modules/view/standard-objects/view-filter.workspace-entity';
|
||||
import { ViewGroupWorkspaceEntity } from 'src/modules/view/standard-objects/view-group.workspace-entity';
|
||||
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:0-43:add-tasks-assigned-to-me-view',
|
||||
description: 'Add tasks assigned to me view',
|
||||
})
|
||||
export class AddTasksAssignedToMeViewCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
index,
|
||||
total,
|
||||
workspaceId,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
|
||||
);
|
||||
|
||||
await this.createTasksAssignedToMeView(workspaceId);
|
||||
|
||||
this.logger.log(
|
||||
chalk.green(`Command completed for workspace ${workspaceId}.`),
|
||||
);
|
||||
}
|
||||
|
||||
private async createTasksAssignedToMeView(
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const objectMetadata = await this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
relations: ['fields'],
|
||||
});
|
||||
|
||||
const objectMetadataMap = objectMetadata.reduce((acc, object) => {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
acc[object.standardId ?? ''] = {
|
||||
id: object.id,
|
||||
fields: object.fields.reduce((acc, field) => {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
acc[field.standardId ?? ''] = field.id;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const taskObjectMetadata = objectMetadata.find(
|
||||
(object) => object.standardId === STANDARD_OBJECT_IDS.task,
|
||||
);
|
||||
|
||||
if (!taskObjectMetadata) {
|
||||
throw new Error(`Task object not found for workspace ${workspaceId}`);
|
||||
}
|
||||
|
||||
const viewRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'view',
|
||||
);
|
||||
|
||||
const existingView = await viewRepository.findOne({
|
||||
where: {
|
||||
name: 'Assigned to Me',
|
||||
objectMetadataId: taskObjectMetadata.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingView) {
|
||||
this.logger.log(
|
||||
chalk.yellow(
|
||||
`"Assigned to Me" view already exists for workspace ${workspaceId}`,
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const viewDefinition = tasksAssignedToMeView(objectMetadataMap);
|
||||
const viewId = v4();
|
||||
|
||||
const insertedView = await viewRepository.save({
|
||||
id: viewId,
|
||||
name: viewDefinition.name,
|
||||
objectMetadataId: viewDefinition.objectMetadataId,
|
||||
type: viewDefinition.type,
|
||||
position: viewDefinition.position,
|
||||
icon: viewDefinition.icon,
|
||||
kanbanFieldMetadataId: viewDefinition.kanbanFieldMetadataId,
|
||||
});
|
||||
|
||||
if (viewDefinition.fields && viewDefinition.fields.length > 0) {
|
||||
const viewFieldRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFieldWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'viewField',
|
||||
);
|
||||
|
||||
const viewFields = viewDefinition.fields.map((field) => ({
|
||||
fieldMetadataId: field.fieldMetadataId,
|
||||
position: field.position,
|
||||
isVisible: field.isVisible,
|
||||
size: field.size,
|
||||
viewId: insertedView.id,
|
||||
}));
|
||||
|
||||
await viewFieldRepository.save(viewFields);
|
||||
}
|
||||
|
||||
if (viewDefinition.filters && viewDefinition.filters.length > 0) {
|
||||
const viewFilterRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewFilterWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'viewFilter',
|
||||
);
|
||||
|
||||
const viewFilters = viewDefinition.filters.map((filter) => ({
|
||||
fieldMetadataId: filter.fieldMetadataId,
|
||||
displayValue: filter.displayValue,
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
viewId: insertedView.id,
|
||||
}));
|
||||
|
||||
await viewFilterRepository.save(viewFilters);
|
||||
}
|
||||
|
||||
await this.createTasksAssignedToMeViewGroups(workspaceId, insertedView.id);
|
||||
}
|
||||
|
||||
private async createTasksAssignedToMeViewGroups(
|
||||
workspaceId: string,
|
||||
viewId: string,
|
||||
) {
|
||||
const taskStatusFieldMetadata = await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
workspaceId,
|
||||
standardId: TASK_STANDARD_FIELD_IDS.status,
|
||||
},
|
||||
});
|
||||
|
||||
if (!taskStatusFieldMetadata) {
|
||||
throw new Error(
|
||||
`Task status field metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const optionValueViewGroups = taskStatusFieldMetadata.options.map(
|
||||
(taskStatusOption: FieldMetadataDefaultOption, index) =>
|
||||
({
|
||||
fieldMetadataId: taskStatusFieldMetadata.id,
|
||||
viewId,
|
||||
fieldValue: taskStatusOption.value,
|
||||
position: index,
|
||||
}) satisfies Partial<ViewGroupWorkspaceEntity>,
|
||||
);
|
||||
|
||||
const noValueViewGroup: Partial<ViewGroupWorkspaceEntity> = {
|
||||
fieldMetadataId: taskStatusFieldMetadata.id,
|
||||
viewId,
|
||||
fieldValue: '',
|
||||
position: optionValueViewGroups.length,
|
||||
};
|
||||
|
||||
const viewGroups = [...optionValueViewGroups, noValueViewGroup];
|
||||
|
||||
const viewGroupRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewGroupWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'viewGroup',
|
||||
);
|
||||
|
||||
await viewGroupRepository.insert(viewGroups);
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:0-43:migrate-is-searchable-for-custom-object-metadata',
|
||||
description: 'Set isSearchable true for custom object metadata',
|
||||
})
|
||||
export class MigrateIsSearchableForCustomObjectMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
index,
|
||||
total,
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
|
||||
);
|
||||
|
||||
if (!options.dryRun) {
|
||||
await this.objectMetadataRepository.update(
|
||||
{
|
||||
workspaceId,
|
||||
isCustom: true,
|
||||
},
|
||||
{
|
||||
isSearchable: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,275 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ServerBlockNoteEditor } from '@blocknote/server-util';
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
|
||||
type MigrateRichTextContentArgs = {
|
||||
richTextFieldsWithObjectMetadata: RichTextFieldsWithObjectMetadata[];
|
||||
workspaceId: string;
|
||||
options: ActiveOrSuspendedWorkspacesMigrationCommandOptions;
|
||||
};
|
||||
|
||||
type RichTextFieldsWithObjectMetadata = {
|
||||
richTextField: FieldMetadataEntity;
|
||||
objectMetadata: ObjectMetadataEntity | null;
|
||||
};
|
||||
|
||||
type ProcessRichTextFieldsArgs = {
|
||||
richTextFields: FieldMetadataEntity[];
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:0-43:migrate-rich-text-content-patch',
|
||||
description: 'Migrate RICH_TEXT content from v1 to v2',
|
||||
})
|
||||
export class MigrateRichTextContentPatchCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FeatureFlag, 'core')
|
||||
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
index,
|
||||
total,
|
||||
options,
|
||||
workspaceId,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running MigrateRichTextContentPatchCommand for workspace ${workspaceId} ${index + 1}/${total}`,
|
||||
);
|
||||
|
||||
if (await this.hasRichTextV2FeatureFlag(workspaceId)) {
|
||||
this.logger.log(
|
||||
chalk.yellow(
|
||||
'Rich text v2 feature flag is enabled, skipping migration',
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const richTextFields = await this.fieldMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
type: FieldMetadataType.RICH_TEXT,
|
||||
},
|
||||
});
|
||||
|
||||
if (!richTextFields.length) {
|
||||
this.logger.log(
|
||||
chalk.yellow('No RICH_TEXT fields found in this workspace'),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(`Found ${richTextFields.length} RICH_TEXT fields`);
|
||||
|
||||
const richTextFieldsWithObjectMetadata =
|
||||
await this.getRichTextFieldsWithObjectMetadata({
|
||||
richTextFields,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.migrateToNewRichTextFieldsColumn({
|
||||
richTextFieldsWithObjectMetadata,
|
||||
workspaceId,
|
||||
options,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
chalk.green(`Command completed for workspace ${workspaceId}`),
|
||||
);
|
||||
}
|
||||
|
||||
private async hasRichTextV2FeatureFlag(
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
return await this.featureFlagRepository.exists({
|
||||
where: {
|
||||
workspaceId,
|
||||
key: 'IS_RICH_TEXT_V2_ENABLED' as FeatureFlagKey,
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getRichTextFieldsWithObjectMetadata({
|
||||
richTextFields,
|
||||
workspaceId,
|
||||
}: ProcessRichTextFieldsArgs): Promise<RichTextFieldsWithObjectMetadata[]> {
|
||||
const richTextFieldsWithObjectMetadata: RichTextFieldsWithObjectMetadata[] =
|
||||
[];
|
||||
|
||||
for (const richTextField of richTextFields) {
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
where: { id: richTextField.objectMetadataId },
|
||||
relations: {
|
||||
fields: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (objectMetadata === null) {
|
||||
this.logger.log(
|
||||
`Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
richTextFieldsWithObjectMetadata.push({
|
||||
richTextField,
|
||||
objectMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
return richTextFieldsWithObjectMetadata;
|
||||
}
|
||||
|
||||
private jsonParseOrSilentlyFail(input: string): null | unknown {
|
||||
try {
|
||||
return JSON.parse(input);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async getMarkdownFieldValue({
|
||||
blocknoteFieldValue,
|
||||
serverBlockNoteEditor,
|
||||
}: {
|
||||
blocknoteFieldValue: string | null;
|
||||
serverBlockNoteEditor: ServerBlockNoteEditor;
|
||||
}): Promise<string | null> {
|
||||
const blocknoteFieldValueIsDefined =
|
||||
blocknoteFieldValue !== null &&
|
||||
blocknoteFieldValue !== undefined &&
|
||||
blocknoteFieldValue !== '{}';
|
||||
|
||||
if (!blocknoteFieldValueIsDefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const jsonParsedblocknoteFieldValue =
|
||||
this.jsonParseOrSilentlyFail(blocknoteFieldValue);
|
||||
|
||||
if (jsonParsedblocknoteFieldValue === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Array.isArray(jsonParsedblocknoteFieldValue)) {
|
||||
this.logger.log(
|
||||
`blocknoteFieldValue is defined and is not an array got ${blocknoteFieldValue}`,
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let markdown: string | null = null;
|
||||
|
||||
try {
|
||||
markdown = await serverBlockNoteEditor.blocksToMarkdownLossy(
|
||||
jsonParsedblocknoteFieldValue,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
`Error converting blocknote to markdown for ${blocknoteFieldValue}`,
|
||||
);
|
||||
}
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
||||
private async migrateToNewRichTextFieldsColumn({
|
||||
richTextFieldsWithObjectMetadata,
|
||||
workspaceId,
|
||||
options,
|
||||
}: MigrateRichTextContentArgs) {
|
||||
const serverBlockNoteEditor = ServerBlockNoteEditor.create();
|
||||
|
||||
for (const {
|
||||
richTextField,
|
||||
objectMetadata,
|
||||
} of richTextFieldsWithObjectMetadata) {
|
||||
if (objectMetadata === null) {
|
||||
this.logger.log(
|
||||
`Object metadata not found for rich text field ${richTextField.name} in workspace ${workspaceId}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const schemaName =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
const rows = await workspaceDataSource.query(
|
||||
`SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" WHERE "${richTextField.name}" IS NOT NULL`,
|
||||
undefined, // parameters
|
||||
undefined, // queryRunner
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(`Generating markdown for ${rows.length} records`);
|
||||
|
||||
for (const row of rows) {
|
||||
const blocknoteFieldValue = row[richTextField.name];
|
||||
const markdownFieldValue = await this.getMarkdownFieldValue({
|
||||
blocknoteFieldValue,
|
||||
serverBlockNoteEditor,
|
||||
});
|
||||
|
||||
if (!options.dryRun) {
|
||||
try {
|
||||
await workspaceDataSource.query(
|
||||
`UPDATE "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" SET "${richTextField.name}V2Blocknote" = $1, "${richTextField.name}V2Markdown" = $2 WHERE id = $3`,
|
||||
[blocknoteFieldValue, markdownFieldValue, row.id],
|
||||
undefined, // queryRunner
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
chalk.red(
|
||||
`Error updating rich text field ${richTextField.name} for record ${row.id} in workspace ${workspaceId}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { SEARCH_FIELDS_FOR_NOTES } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { SEARCH_FIELDS_FOR_TASKS } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:0-43:migrate-search-vector-on-note-and-task-entities',
|
||||
description: 'Migrate search vector on note and task entities',
|
||||
})
|
||||
export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectRepository(FeatureFlag, 'core')
|
||||
protected readonly featureFlagRepository: Repository<FeatureFlag>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly searchVectorService: SearchVectorService,
|
||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
index,
|
||||
total,
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
|
||||
);
|
||||
|
||||
const noteObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
select: ['id'],
|
||||
where: {
|
||||
workspaceId,
|
||||
nameSingular: 'note',
|
||||
},
|
||||
});
|
||||
|
||||
if (!options.dryRun) {
|
||||
await this.searchVectorService.updateSearchVector(
|
||||
noteObjectMetadata.id,
|
||||
SEARCH_FIELDS_FOR_NOTES,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
const taskObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
select: ['id'],
|
||||
where: {
|
||||
workspaceId,
|
||||
nameSingular: 'task',
|
||||
},
|
||||
});
|
||||
|
||||
if (!options.dryRun) {
|
||||
await this.searchVectorService.updateSearchVector(
|
||||
taskObjectMetadata.id,
|
||||
SEARCH_FIELDS_FOR_TASKS,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Migrated search vector on note and task entities for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:0-43:update-default-view-record-opening-on-workflow-objects',
|
||||
description:
|
||||
'Update default view record opening on workflow objects to record page',
|
||||
})
|
||||
export class UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
index,
|
||||
total,
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running command for workspace ${workspaceId} ${index + 1}/${total}`,
|
||||
);
|
||||
|
||||
const workflowObjectsMetadata = await this.objectMetadataRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
workspaceId,
|
||||
standardId: In([
|
||||
STANDARD_OBJECT_IDS.workflow,
|
||||
STANDARD_OBJECT_IDS.workflowVersion,
|
||||
STANDARD_OBJECT_IDS.workflowRun,
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
if (workflowObjectsMetadata.length === 0) {
|
||||
this.logger.log(
|
||||
chalk.yellow(`No workflow objects found for workspace ${workspaceId}`),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
await this.updateDefaultViewsRecordOpening(
|
||||
workflowObjectsMetadata.map((metadata) => metadata.id),
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
chalk.green(`Command completed for workspace ${workspaceId}.`),
|
||||
);
|
||||
}
|
||||
|
||||
private async updateDefaultViewsRecordOpening(
|
||||
workflowObjectMetadataIds: string[],
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const failOnMetadataCacheMiss = false;
|
||||
const viewRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
workspaceId,
|
||||
'view',
|
||||
{
|
||||
shouldFailIfMetadataNotFound: failOnMetadataCacheMiss,
|
||||
},
|
||||
);
|
||||
|
||||
await viewRepository.update(
|
||||
{
|
||||
objectMetadataId: In(workflowObjectMetadataIds),
|
||||
key: 'INDEX',
|
||||
},
|
||||
{
|
||||
openRecordIn: ViewOpenRecordInType.RECORD_PAGE,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command';
|
||||
import { MigrateIsSearchableForCustomObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command';
|
||||
import { MigrateRichTextContentPatchCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command';
|
||||
import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command';
|
||||
import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module';
|
||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace, FeatureFlag], 'core'),
|
||||
TypeOrmModule.forFeature(
|
||||
[FieldMetadataEntity, ObjectMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
WorkspaceDataSourceModule,
|
||||
SearchVectorModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
WorkspaceMetadataVersionModule,
|
||||
],
|
||||
providers: [
|
||||
MigrateRichTextContentPatchCommand,
|
||||
MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
|
||||
UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
|
||||
MigrateIsSearchableForCustomObjectMetadataCommand,
|
||||
AddTasksAssignedToMeViewCommand,
|
||||
],
|
||||
exports: [
|
||||
MigrateRichTextContentPatchCommand,
|
||||
MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
|
||||
UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
|
||||
MigrateIsSearchableForCustomObjectMetadataCommand,
|
||||
AddTasksAssignedToMeViewCommand,
|
||||
],
|
||||
})
|
||||
export class V0_43_UpgradeVersionCommandModule {}
|
||||
@ -1,7 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { V0_43_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-43/0-43-upgrade-version-command.module';
|
||||
import { V0_44_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-44/0-44-upgrade-version-command.module';
|
||||
import { V0_50_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-50/0-50-upgrade-version-command.module';
|
||||
import { V0_51_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/0-51/0-51-upgrade-version-command.module';
|
||||
@ -19,7 +18,6 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||
V0_43_UpgradeVersionCommandModule,
|
||||
V0_44_UpgradeVersionCommandModule,
|
||||
V0_50_UpgradeVersionCommandModule,
|
||||
V0_51_UpgradeVersionCommandModule,
|
||||
|
||||
@ -15,11 +15,6 @@ import {
|
||||
UpgradeCommandRunner,
|
||||
VersionCommands,
|
||||
} from 'src/database/commands/command-runners/upgrade.command-runner';
|
||||
import { AddTasksAssignedToMeViewCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-add-tasks-assigned-to-me-view.command';
|
||||
import { MigrateIsSearchableForCustomObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-is-searchable-for-custom-object-metadata.command';
|
||||
import { MigrateRichTextContentPatchCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-rich-text-content-patch.command';
|
||||
import { MigrateSearchVectorOnNoteAndTaskEntitiesCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-migrate-search-vector-on-note-and-task-entities.command';
|
||||
import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/database/commands/upgrade-version-command/0-43/0-43-update-default-view-record-opening-on-workflow-objects.command';
|
||||
import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command';
|
||||
import { UpdateViewAggregateOperationsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-update-view-aggregate-operations.command';
|
||||
import { UpgradeCreatedByEnumCommand } from 'src/database/commands/upgrade-version-command/0-51/0-51-update-workflow-trigger-type-enum.command';
|
||||
@ -152,13 +147,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
|
||||
private readonly databaseMigrationService: DatabaseMigrationService,
|
||||
|
||||
// 0.43 Commands
|
||||
protected readonly migrateRichTextContentPatchCommand: MigrateRichTextContentPatchCommand,
|
||||
protected readonly addTasksAssignedToMeViewCommand: AddTasksAssignedToMeViewCommand,
|
||||
protected readonly migrateIsSearchableForCustomObjectMetadataCommand: MigrateIsSearchableForCustomObjectMetadataCommand,
|
||||
protected readonly updateDefaultViewRecordOpeningOnWorkflowObjectsCommand: UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
|
||||
protected readonly migrateSearchVectorOnNoteAndTaskEntitiesCommand: MigrateSearchVectorOnNoteAndTaskEntitiesCommand,
|
||||
|
||||
// 0.44 Commands
|
||||
protected readonly initializePermissionsCommand: InitializePermissionsCommand,
|
||||
protected readonly updateViewAggregateOperationsCommand: UpdateViewAggregateOperationsCommand,
|
||||
@ -191,18 +179,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
syncWorkspaceMetadataCommand,
|
||||
);
|
||||
|
||||
const commands_043: VersionCommands = {
|
||||
beforeSyncMetadata: [
|
||||
this.migrateRichTextContentPatchCommand,
|
||||
this.migrateIsSearchableForCustomObjectMetadataCommand,
|
||||
this.migrateSearchVectorOnNoteAndTaskEntitiesCommand,
|
||||
this.migrateIsSearchableForCustomObjectMetadataCommand,
|
||||
],
|
||||
afterSyncMetadata: [
|
||||
this.updateDefaultViewRecordOpeningOnWorkflowObjectsCommand,
|
||||
this.addTasksAssignedToMeViewCommand,
|
||||
],
|
||||
};
|
||||
const commands_044: VersionCommands = {
|
||||
beforeSyncMetadata: [
|
||||
this.initializePermissionsCommand,
|
||||
@ -251,7 +227,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
};
|
||||
|
||||
this.allCommands = {
|
||||
'0.43.0': commands_043,
|
||||
'0.44.0': commands_044,
|
||||
'0.50.0': commands_050,
|
||||
'0.51.0': commands_051,
|
||||
|
||||
@ -40,6 +40,11 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_AI_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
};
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateServerlessFunctionDefaultRuntimeToNode221749205425841
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'UpdateServerlessFunctionDefaultRuntimeToNode221749205425841';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Update the default value for the runtime column to nodejs22.x
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."serverlessFunction" ALTER COLUMN "runtime" SET DEFAULT 'nodejs22.x'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Revert the default value back to nodejs18.x
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."serverlessFunction" ALTER COLUMN "runtime" SET DEFAULT 'nodejs18.x'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ServerBlockNoteEditor } from '@blocknote/server-util';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -101,6 +100,8 @@ export class RecordInputTransformerService {
|
||||
): Promise<RichTextV2Metadata> {
|
||||
const parsedValue = richTextV2ValueSchema.parse(richTextValue);
|
||||
|
||||
const { ServerBlockNoteEditor } = await import('@blocknote/server-util');
|
||||
|
||||
const serverBlockNoteEditor = ServerBlockNoteEditor.create();
|
||||
|
||||
// Patch: Handle cases where blocknote to markdown conversion fails for certain block types (custom/code blocks)
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
import ts, { transpileModule } from 'typescript';
|
||||
import {
|
||||
CreateFunctionCommandInput,
|
||||
CreateFunctionCommand,
|
||||
CreateFunctionCommandInput,
|
||||
DeleteFunctionCommand,
|
||||
GetFunctionCommand,
|
||||
InvokeCommand,
|
||||
@ -13,14 +12,15 @@ import {
|
||||
LambdaClientConfig,
|
||||
ListLayerVersionsCommand,
|
||||
ListLayerVersionsCommandInput,
|
||||
LogType,
|
||||
PublishLayerVersionCommand,
|
||||
PublishLayerVersionCommandInput,
|
||||
ResourceNotFoundException,
|
||||
waitUntilFunctionUpdatedV2,
|
||||
LogType,
|
||||
} from '@aws-sdk/client-lambda';
|
||||
import { AssumeRoleCommand, STSClient } from '@aws-sdk/client-sts';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import ts, { transpileModule } from 'typescript';
|
||||
|
||||
import {
|
||||
ServerlessDriver,
|
||||
@ -28,8 +28,11 @@ import {
|
||||
} from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content';
|
||||
import { COMMON_LAYER_NAME } from 'src/engine/core-modules/serverless/drivers/constants/common-layer-name';
|
||||
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
|
||||
import { copyAndBuildDependencies } from 'src/engine/core-modules/serverless/drivers/utils/copy-and-build-dependencies';
|
||||
import { copyExecutor } from 'src/engine/core-modules/serverless/drivers/utils/copy-executor';
|
||||
import { createZipFile } from 'src/engine/core-modules/serverless/drivers/utils/create-zip-file';
|
||||
import {
|
||||
LambdaBuildDirectoryManager,
|
||||
@ -45,9 +48,6 @@ import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { copyExecutor } from 'src/engine/core-modules/serverless/drivers/utils/copy-executor';
|
||||
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
|
||||
import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content';
|
||||
|
||||
const UPDATE_FUNCTION_DURATION_TIMEOUT_IN_SECONDS = 60;
|
||||
const CREDENTIALS_DURATION_IN_SECONDS = 60 * 60; // 1h
|
||||
@ -172,7 +172,10 @@ export class LambdaDriver implements ServerlessDriver {
|
||||
Content: {
|
||||
ZipFile: await fs.readFile(lambdaZipPath),
|
||||
},
|
||||
CompatibleRuntimes: [ServerlessFunctionRuntime.NODE18],
|
||||
CompatibleRuntimes: [
|
||||
ServerlessFunctionRuntime.NODE18,
|
||||
ServerlessFunctionRuntime.NODE22,
|
||||
],
|
||||
Description: `${version}`,
|
||||
};
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ const DEFAULT_SERVERLESS_TIMEOUT_SECONDS = 300; // 5 minutes
|
||||
|
||||
export enum ServerlessFunctionRuntime {
|
||||
NODE18 = 'nodejs18.x',
|
||||
NODE22 = 'nodejs22.x',
|
||||
}
|
||||
|
||||
@Entity('serverlessFunction')
|
||||
@ -38,7 +39,7 @@ export class ServerlessFunctionEntity {
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
latestVersionInputSchema: InputSchema;
|
||||
|
||||
@Column({ nullable: false, default: ServerlessFunctionRuntime.NODE18 })
|
||||
@Column({ nullable: false, default: ServerlessFunctionRuntime.NODE22 })
|
||||
runtime: ServerlessFunctionRuntime;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_SERVERLESS_TIMEOUT_SECONDS })
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"build": "preconstruct build"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.1",
|
||||
"node": "^22.12.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": "^4.0.2"
|
||||
},
|
||||
|
||||
@ -21,8 +21,7 @@ const jestConfig: JestConfigWithTsJest = {
|
||||
],
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'\\.(jpg|jpeg|png|gif|webp|svg|svg)$':
|
||||
'<rootDir>/__mocks__/imageMock.js',
|
||||
'\\.(jpg|jpeg|png|gif|webp|svg|svg)$': '<rootDir>/__mocks__/imageMock.js',
|
||||
...pathsToModuleNameMapper(tsConfig.compilerOptions.paths, {
|
||||
prefix: '<rootDir>/',
|
||||
}),
|
||||
|
||||
@ -12,7 +12,7 @@ info: The guide for contributors (or curious developers) who want to run Twenty
|
||||
|
||||
Before you can install and use Twenty, make sure you install the following on your computer:
|
||||
- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
- [Node v18](https://nodejs.org/en/download)
|
||||
- [Node v22](https://nodejs.org/en/download)
|
||||
- [yarn v4](https://yarnpkg.com/getting-started/install)
|
||||
- [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md)
|
||||
|
||||
@ -265,4 +265,4 @@ You can log in using the default demo account: `tim@apple.dev` (password: `tim@a
|
||||
|
||||
If you encounter any problem, check [Troubleshooting](https://twenty.com/developers/section/self-hosting/troubleshooting) for solutions.
|
||||
|
||||
<ArticleEditContent></ArticleEditContent>
|
||||
<ArticleEditContent />
|
||||
@ -12,7 +12,7 @@
|
||||
"watch": "yarn clean && npx tsc --watch"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.1",
|
||||
"node": "^22.12.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": "^4.0.2"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user