mirror of
https://github.com/twentyhq/twenty.git
synced 2024-08-17 01:40:44 +03:00
POC: chore: use Nx workspace lint rules (#3163)
* chore: use Nx workspace lint rules Closes #3162 * Fix lint * Fix lint on BE * Fix tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
1924962e8c
commit
8483cf0b4b
99
.eslintrc.js
Normal file
99
.eslintrc.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['plugin:prettier/recommended'],
|
||||||
|
plugins: ['@nx', 'prefer-arrow', 'simple-import-sort', 'unused-imports'],
|
||||||
|
rules: {
|
||||||
|
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
|
||||||
|
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'no-control-regex': 0,
|
||||||
|
'no-undef': 'off',
|
||||||
|
|
||||||
|
'@nx/enforce-module-boundaries': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
enforceBuildableLibDependency: true,
|
||||||
|
allow: [],
|
||||||
|
depConstraints: [
|
||||||
|
{
|
||||||
|
sourceTag: '*',
|
||||||
|
onlyDependOnLibsWithTags: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'prefer-arrow/prefer-arrow-functions': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
disallowPrototype: true,
|
||||||
|
singleReturnOnly: false,
|
||||||
|
classPropertiesAllowed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'simple-import-sort/imports': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
['^react', '^@?\\w'],
|
||||||
|
['^(@|~)(/.*|$)'],
|
||||||
|
['^\\u0000'],
|
||||||
|
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
|
||||||
|
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
||||||
|
['^.+\\.?(css)$'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'simple-import-sort/exports': 'error',
|
||||||
|
|
||||||
|
'unused-imports/no-unused-imports': 'warn',
|
||||||
|
'unused-imports/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
vars: 'all',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
args: 'after-used',
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
extends: ['plugin:@nx/typescript'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/ban-ts-comment': 'error',
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
vars: 'all',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
args: 'after-used',
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-type-imports': [
|
||||||
|
'error',
|
||||||
|
{ prefer: 'no-type-imports' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.js', '*.jsx'],
|
||||||
|
extends: ['plugin:@nx/javascript'],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.spec.@(ts|tsx|js|jsx)', '*.test.@(ts|tsx|js|jsx)'],
|
||||||
|
env: {
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -16,7 +16,6 @@
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
coverage
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
# yarn.lock should be managed at the root level only (nx monorepo)
|
|
||||||
**/yarn.lock
|
**/yarn.lock
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Add files here to ignore them from prettier formatting
|
||||||
|
/dist
|
||||||
|
/coverage
|
||||||
|
/.nx/cache
|
20
.vscode/extensions.json
vendored
20
.vscode/extensions.json
vendored
@ -1,17 +1,19 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"ms-vscode-remote.remote-containers",
|
"arcanis.vscode-zipfs",
|
||||||
"styled-components.vscode-styled-components",
|
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"unifiedjs.vscode-mdx",
|
|
||||||
"ms-vscode.makefile-tools",
|
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"GraphQL.vscode-graphql",
|
|
||||||
"yoavbls.pretty-ts-errors",
|
|
||||||
"graphql.vscode-graphql-syntax",
|
|
||||||
"graphql.vscode-graphql",
|
|
||||||
"figma.figma-vscode-extension",
|
"figma.figma-vscode-extension",
|
||||||
|
"firsttris.vscode-jest-runner",
|
||||||
|
"graphql.vscode-graphql-syntax",
|
||||||
|
"GraphQL.vscode-graphql",
|
||||||
|
"graphql.vscode-graphql",
|
||||||
|
"ms-vscode-remote.remote-containers",
|
||||||
|
"ms-vscode.makefile-tools",
|
||||||
|
"nrwl.angular-console",
|
||||||
|
"styled-components.vscode-styled-components",
|
||||||
|
"unifiedjs.vscode-mdx",
|
||||||
"xyc.vscode-mdx-preview",
|
"xyc.vscode-mdx-preview",
|
||||||
"arcanis.vscode-zipfs"
|
"yoavbls.pretty-ts-errors"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
8
.vscode/twenty.code-workspace
vendored
8
.vscode/twenty.code-workspace
vendored
@ -4,10 +4,6 @@
|
|||||||
"name": "ROOT",
|
"name": "ROOT",
|
||||||
"path": "../"
|
"path": "../"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "packages/eslint-plugin-twenty",
|
|
||||||
"path": "../packages/eslint-plugin-twenty"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "packages/twenty-docker",
|
"name": "packages/twenty-docker",
|
||||||
"path": "../packages/twenty-docker"
|
"path": "../packages/twenty-docker"
|
||||||
@ -36,6 +32,10 @@
|
|||||||
"name": "packages/twenty-zapier",
|
"name": "packages/twenty-zapier",
|
||||||
"path": "../packages/twenty-zapier"
|
"path": "../packages/twenty-zapier"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tools/eslint-rules",
|
||||||
|
"path": "../tools/eslint-rules"
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
|
5
jest.config.js
Normal file
5
jest.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { getJestProjects } from '@nx/jest';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
projects: getJestProjects(),
|
||||||
|
};
|
3
jest.preset.js
Normal file
3
jest.preset.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const nxPreset = require('@nx/jest/preset').default;
|
||||||
|
|
||||||
|
module.exports = { ...nxPreset };
|
24
nx.json
24
nx.json
@ -2,7 +2,9 @@
|
|||||||
"targetDefaults": {
|
"targetDefaults": {
|
||||||
"build": {
|
"build": {
|
||||||
"cache": true,
|
"cache": true,
|
||||||
"dependsOn": ["^build"]
|
"dependsOn": [
|
||||||
|
"^build"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"cache": true
|
"cache": true
|
||||||
@ -12,12 +14,30 @@
|
|||||||
},
|
},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
"cache": true
|
"cache": true
|
||||||
|
},
|
||||||
|
"@nx/jest:jest": {
|
||||||
|
"cache": true,
|
||||||
|
"inputs": [
|
||||||
|
"default",
|
||||||
|
"^default",
|
||||||
|
"{workspaceRoot}/jest.preset.js"
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"passWithNoTests": true
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"ci": true,
|
||||||
|
"codeCoverage": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"installation": {
|
"installation": {
|
||||||
"version": "17.2.0"
|
"version": "17.2.7"
|
||||||
},
|
},
|
||||||
"affected": {
|
"affected": {
|
||||||
"defaultBase": "main"
|
"defaultBase": "main"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
package.json
85
package.json
@ -10,20 +10,95 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/eslint-plugin-twenty",
|
|
||||||
"packages/twenty-front",
|
"packages/twenty-front",
|
||||||
"packages/twenty-docs",
|
"packages/twenty-docs",
|
||||||
"packages/twenty-server",
|
"packages/twenty-server",
|
||||||
"packages/twenty-utils",
|
"packages/twenty-utils",
|
||||||
"packages/twenty-zapier",
|
"packages/twenty-zapier",
|
||||||
"packages/twenty-website"
|
"packages/twenty-website",
|
||||||
|
"tools/eslint-rules"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nx": "17.2.0",
|
"@graphql-codegen/cli": "^3.3.1",
|
||||||
"typescript": "^5.3.3"
|
"@graphql-codegen/client-preset": "^4.1.0",
|
||||||
|
"@graphql-codegen/typescript": "^3.0.4",
|
||||||
|
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||||
|
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||||
|
"@nx/eslint": "17.2.7",
|
||||||
|
"@nx/eslint-plugin": "17.2.7",
|
||||||
|
"@nx/jest": "17.2.7",
|
||||||
|
"@nx/js": "17.2.7",
|
||||||
|
"@storybook/addon-actions": "^7.6.3",
|
||||||
|
"@storybook/addon-coverage": "^1.0.0",
|
||||||
|
"@storybook/addon-essentials": "^7.6.3",
|
||||||
|
"@storybook/addon-interactions": "^7.6.3",
|
||||||
|
"@storybook/addon-links": "^7.6.3",
|
||||||
|
"@storybook/addon-onboarding": "^1.0.9",
|
||||||
|
"@storybook/addon-themes": "^7.6.3",
|
||||||
|
"@storybook/blocks": "^7.6.3",
|
||||||
|
"@storybook/react": "^7.6.3",
|
||||||
|
"@storybook/react-vite": "^7.6.3",
|
||||||
|
"@storybook/test": "^7.6.3",
|
||||||
|
"@storybook/test-runner": "^0.16.0",
|
||||||
|
"@stylistic/eslint-plugin": "^1.5.0",
|
||||||
|
"@swc-node/register": "~1.6.7",
|
||||||
|
"@swc/core": "~1.3.100",
|
||||||
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@types/apollo-upload-client": "^17.0.2",
|
||||||
|
"@types/deep-equal": "^1.0.1",
|
||||||
|
"@types/jest": "^29.5.11",
|
||||||
|
"@types/js-cookie": "^3.0.3",
|
||||||
|
"@types/lodash.camelcase": "^4.3.7",
|
||||||
|
"@types/lodash.debounce": "^4.0.7",
|
||||||
|
"@types/lodash.kebabcase": "^4.1.7",
|
||||||
|
"@types/lodash.snakecase": "^4.1.9",
|
||||||
|
"@types/luxon": "^3.3.0",
|
||||||
|
"@types/node": "20.10.0",
|
||||||
|
"@types/react": "^18.2.39",
|
||||||
|
"@types/react-datepicker": "^4.11.2",
|
||||||
|
"@types/react-dom": "^18.2.15",
|
||||||
|
"@types/scroll-into-view": "^1.16.0",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||||
|
"@typescript-eslint/parser": "^6.10.0",
|
||||||
|
"@typescript-eslint/utils": "^6.9.1",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
|
"chromatic": "^6.18.0",
|
||||||
|
"concurrently": "^8.0.1",
|
||||||
|
"cross-var": "^1.1.0",
|
||||||
|
"dotenv-cli": "^7.2.1",
|
||||||
|
"eslint": "^8.53.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||||
|
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||||
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.4",
|
||||||
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
|
"eslint-plugin-storybook": "^0.6.15",
|
||||||
|
"eslint-plugin-unused-imports": "^3.0.0",
|
||||||
|
"http-server": "^14.1.1",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"jest-environment-jsdom": "29.7.0",
|
||||||
|
"msw": "^2.0.11",
|
||||||
|
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
|
||||||
|
"nx": "17.2.7",
|
||||||
|
"prettier": "3.1.0",
|
||||||
|
"storybook": "^7.6.3",
|
||||||
|
"storybook-addon-cookie": "^3.1.0",
|
||||||
|
"storybook-addon-pseudo-states": "^2.1.2",
|
||||||
|
"ts-jest": "^29.1.1",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vite-plugin-svgr": "^4.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"patch-package": "^8.0.0"
|
"patch-package": "^8.0.0",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: 'tsconfig.json',
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
'@typescript-eslint/eslint-plugin',
|
|
||||||
'unused-imports',
|
|
||||||
'simple-import-sort',
|
|
||||||
'prefer-arrow',
|
|
||||||
],
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
],
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
|
|
||||||
rules: {
|
|
||||||
'no-control-regex': 0,
|
|
||||||
'simple-import-sort/imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
groups: [
|
|
||||||
['^react', '^@?\\w'],
|
|
||||||
['^(@|~)(/.*|$)'],
|
|
||||||
['^\\u0000'],
|
|
||||||
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
|
|
||||||
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
|
||||||
['^.+\\.?(css)$']
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'prefer-arrow/prefer-arrow-functions': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
"disallowPrototype": true,
|
|
||||||
"singleReturnOnly": false,
|
|
||||||
"classPropertiesAllowed": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
ignorePatterns: ['.eslintrc.js', 'codegen.js', '**/generated/*', '*.config.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'simple-import-sort/imports': 'error',
|
|
||||||
'simple-import-sort/exports': 'error',
|
|
||||||
'func-style':['error', 'declaration', { 'allowArrowFunctions': true }],
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"unused-imports/no-unused-imports": "warn",
|
|
||||||
"unused-imports/no-unused-vars": [
|
|
||||||
"warn",
|
|
||||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
|
||||||
],
|
|
||||||
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "no-type-imports" }],
|
|
||||||
}
|
|
||||||
};
|
|
1
packages/eslint-plugin-twenty/.gitignore
vendored
1
packages/eslint-plugin-twenty/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
dist/
|
|
Binary file not shown.
@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
rules: {
|
|
||||||
"effect-components": require("./src/rules/effect-components"),
|
|
||||||
"no-hardcoded-colors": require("./src/rules/no-hardcoded-colors"),
|
|
||||||
"matching-state-variable": require("./src/rules/matching-state-variable"),
|
|
||||||
"sort-css-properties-alphabetically": require("./src/rules/sort-css-properties-alphabetically"),
|
|
||||||
"styled-components-prefixed-with-styled": require("./src/rules/styled-components-prefixed-with-styled"),
|
|
||||||
"component-props-naming": require("./src/rules/component-props-naming"),
|
|
||||||
"no-state-useref": require("./src/rules/no-state-useref"),
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
||||||
module.exports = {
|
|
||||||
preset: 'ts-jest',
|
|
||||||
testEnvironment: 'node',
|
|
||||||
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$',
|
|
||||||
"moduleDirectories": ["node_modules"]
|
|
||||||
};
|
|
@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "eslint-plugin-twenty",
|
|
||||||
"version": "1.0.3",
|
|
||||||
"description": "",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"files": [
|
|
||||||
"dist",
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"test": "jest",
|
|
||||||
"build": "rimraf ./dist && tsc --outDir ./dist",
|
|
||||||
"build-pack": "yarn build && yarn pack -o eslint-plugin-twenty.tgz",
|
|
||||||
"lint": "eslint src --max-warnings=0"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/jest": "^29.5.4",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
|
||||||
"@typescript-eslint/parser": "^6.7.0",
|
|
||||||
"@typescript-eslint/rule-tester": "^6.7.0",
|
|
||||||
"@typescript-eslint/utils": "^6.7.0",
|
|
||||||
"eslint": "^8.49.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-config-standard-with-typescript": "^39.0.0",
|
|
||||||
"eslint-plugin-import": "^2.28.1",
|
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
||||||
"eslint-plugin-unused-imports": "^3.0.0",
|
|
||||||
"jest": "^28.1.3",
|
|
||||||
"postcss": "^8.4.29",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"rimraf": "^5.0.5",
|
|
||||||
"ts-jest": "^29.1.1",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"typescript": "^5.2.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
import {
|
|
||||||
AST_NODE_TYPES,
|
|
||||||
ESLintUtils,
|
|
||||||
TSESTree,
|
|
||||||
} from "@typescript-eslint/utils";
|
|
||||||
import { RuleContext } from "@typescript-eslint/utils/ts-eslint";
|
|
||||||
|
|
||||||
const createRule = ESLintUtils.RuleCreator(
|
|
||||||
() => "https://docs.twenty.com/developer/frontend/style-guide#props",
|
|
||||||
);
|
|
||||||
|
|
||||||
const checkPropsTypeName = (
|
|
||||||
node: TSESTree.FunctionDeclaration | TSESTree.ArrowFunctionExpression,
|
|
||||||
context: Readonly<RuleContext<"invalidPropsTypeName", never[]>>,
|
|
||||||
functionName: string,
|
|
||||||
) => {
|
|
||||||
const expectedPropTypeName = `${functionName}Props`;
|
|
||||||
|
|
||||||
if (/^[A-Z]/.test(functionName)) {
|
|
||||||
node.params.forEach((param) => {
|
|
||||||
if (
|
|
||||||
(param.type === AST_NODE_TYPES.ObjectPattern ||
|
|
||||||
param.type === AST_NODE_TYPES.Identifier) &&
|
|
||||||
param.typeAnnotation?.typeAnnotation?.type ===
|
|
||||||
AST_NODE_TYPES.TSTypeReference &&
|
|
||||||
param.typeAnnotation.typeAnnotation.typeName.type ===
|
|
||||||
AST_NODE_TYPES.Identifier
|
|
||||||
) {
|
|
||||||
const { typeName } = param.typeAnnotation.typeAnnotation;
|
|
||||||
const actualPropTypeName = typeName.name;
|
|
||||||
if (actualPropTypeName !== expectedPropTypeName) {
|
|
||||||
context.report({
|
|
||||||
node: param,
|
|
||||||
messageId: "invalidPropsTypeName",
|
|
||||||
data: { expectedPropTypeName, actualPropTypeName },
|
|
||||||
fix: (fixer) => fixer.replaceText(typeName, expectedPropTypeName),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const componentPropsNamingRule = createRule({
|
|
||||||
create: (context) => {
|
|
||||||
return {
|
|
||||||
ArrowFunctionExpression: (node) => {
|
|
||||||
if (
|
|
||||||
node.parent.type === AST_NODE_TYPES.VariableDeclarator &&
|
|
||||||
node.parent.id.type === AST_NODE_TYPES.Identifier
|
|
||||||
) {
|
|
||||||
const functionName = node.parent?.id?.name;
|
|
||||||
|
|
||||||
checkPropsTypeName(node, context, functionName);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FunctionDeclaration: (node) => {
|
|
||||||
if (node.id?.name) {
|
|
||||||
const functionName = node.id.name;
|
|
||||||
|
|
||||||
checkPropsTypeName(node, context, functionName);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
name: "component-props-naming",
|
|
||||||
meta: {
|
|
||||||
type: "problem",
|
|
||||||
docs: {
|
|
||||||
description: "Ensure component props follow naming convention",
|
|
||||||
recommended: "recommended",
|
|
||||||
},
|
|
||||||
fixable: "code",
|
|
||||||
schema: [],
|
|
||||||
messages: {
|
|
||||||
invalidPropsTypeName:
|
|
||||||
"Expected prop type to be '{{ expectedPropTypeName }}' but found '{{ actualPropTypeName }}'",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = componentPropsNamingRule;
|
|
||||||
export default componentPropsNamingRule;
|
|
@ -1,166 +0,0 @@
|
|||||||
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
|
|
||||||
|
|
||||||
const createRule = ESLintUtils.RuleCreator(() => `https://docs.twenty.com`);
|
|
||||||
|
|
||||||
const checkIsPascalCase = (input: string): boolean => {
|
|
||||||
const pascalCaseRegex = /^[A-Z][a-zA-Z0-9_]*/g;
|
|
||||||
|
|
||||||
return pascalCaseRegex.test(input);
|
|
||||||
};
|
|
||||||
|
|
||||||
type ComponentType =
|
|
||||||
| TSESTree.FunctionDeclaration
|
|
||||||
| TSESTree.ArrowFunctionExpression
|
|
||||||
| TSESTree.FunctionExpression;
|
|
||||||
|
|
||||||
const effectComponentsRule = createRule({
|
|
||||||
create: (context) => {
|
|
||||||
const checkThatNodeIsEffectComponent = (node: ComponentType) => {
|
|
||||||
let componentName = "";
|
|
||||||
let identifierNode = node.id;
|
|
||||||
|
|
||||||
const isIdentifier = (
|
|
||||||
node: TSESTree.Node | null,
|
|
||||||
): node is TSESTree.Identifier =>
|
|
||||||
node?.type === TSESTree.AST_NODE_TYPES.Identifier;
|
|
||||||
const isVariableDeclarator = (
|
|
||||||
node: TSESTree.Node,
|
|
||||||
): node is TSESTree.VariableDeclarator =>
|
|
||||||
node.type === TSESTree.AST_NODE_TYPES.VariableDeclarator;
|
|
||||||
|
|
||||||
const isArrowFunction = (
|
|
||||||
node: TSESTree.Node,
|
|
||||||
): node is TSESTree.ArrowFunctionExpression =>
|
|
||||||
node.type === TSESTree.AST_NODE_TYPES.ArrowFunctionExpression;
|
|
||||||
const isFunctionDeclaration = (
|
|
||||||
node: TSESTree.Node,
|
|
||||||
): node is TSESTree.FunctionDeclaration =>
|
|
||||||
node.type === TSESTree.AST_NODE_TYPES.FunctionDeclaration;
|
|
||||||
const isFunctionExpression = (
|
|
||||||
node: TSESTree.Node,
|
|
||||||
): node is TSESTree.FunctionExpression =>
|
|
||||||
node.type === TSESTree.AST_NODE_TYPES.FunctionExpression;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isArrowFunction(node) &&
|
|
||||||
isVariableDeclarator(node.parent) &&
|
|
||||||
isIdentifier(node.parent.id)
|
|
||||||
) {
|
|
||||||
componentName = node.parent.id.name;
|
|
||||||
identifierNode = node.parent.id;
|
|
||||||
} else if (isFunctionDeclaration(node) && isIdentifier(node.id)) {
|
|
||||||
componentName = node.id.name;
|
|
||||||
identifierNode = node.id;
|
|
||||||
} else if (
|
|
||||||
isFunctionExpression(node) &&
|
|
||||||
isVariableDeclarator(node.parent) &&
|
|
||||||
isIdentifier(node.parent.id)
|
|
||||||
) {
|
|
||||||
componentName = node.parent.id.name;
|
|
||||||
identifierNode = node.parent.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkIsPascalCase(componentName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isReturningEmptyFragmentOrNull =
|
|
||||||
// Direct return of JSX fragment, e.g., () => <></>
|
|
||||||
(node.body.type === "JSXFragment" && node.body.children.length === 0) ||
|
|
||||||
// Direct return of null, e.g., () => null
|
|
||||||
(node.body.type === "Literal" && node.body.value === null) ||
|
|
||||||
// Return JSX fragment or null from block
|
|
||||||
(node.body.type === "BlockStatement" &&
|
|
||||||
node.body.body.some(
|
|
||||||
(statement) =>
|
|
||||||
statement.type === "ReturnStatement" &&
|
|
||||||
// Empty JSX fragment return, e.g., return <></>;
|
|
||||||
((statement.argument?.type === "JSXFragment" &&
|
|
||||||
statement.argument.children.length === 0) ||
|
|
||||||
// Empty React.Fragment return, e.g., return <React.Fragment></React.Fragment>;
|
|
||||||
(statement.argument?.type === "JSXElement" &&
|
|
||||||
statement.argument.openingElement.name.type ===
|
|
||||||
"JSXIdentifier" &&
|
|
||||||
statement.argument.openingElement.name.name ===
|
|
||||||
"React.Fragment" &&
|
|
||||||
statement.argument.children.length === 0) ||
|
|
||||||
// Literal null return, e.g., return null;
|
|
||||||
(statement.argument?.type === "Literal" &&
|
|
||||||
statement.argument.value === null)),
|
|
||||||
));
|
|
||||||
|
|
||||||
const hasEffectSuffix = componentName.endsWith("Effect");
|
|
||||||
|
|
||||||
const hasEffectSuffixButIsNotEffectComponent =
|
|
||||||
hasEffectSuffix && !isReturningEmptyFragmentOrNull;
|
|
||||||
const isEffectComponentButDoesNotHaveEffectSuffix =
|
|
||||||
!hasEffectSuffix && isReturningEmptyFragmentOrNull;
|
|
||||||
|
|
||||||
if (isEffectComponentButDoesNotHaveEffectSuffix) {
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "effectSuffix",
|
|
||||||
data: {
|
|
||||||
componentName: componentName,
|
|
||||||
},
|
|
||||||
fix: (fixer) => {
|
|
||||||
if (isArrowFunction(node))
|
|
||||||
if (identifierNode) {
|
|
||||||
return fixer.replaceText(
|
|
||||||
identifierNode,
|
|
||||||
componentName + "Effect",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (hasEffectSuffixButIsNotEffectComponent) {
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "noEffectSuffix",
|
|
||||||
data: {
|
|
||||||
componentName: componentName,
|
|
||||||
},
|
|
||||||
fix: (fixer) => {
|
|
||||||
if (identifierNode) {
|
|
||||||
return fixer.replaceText(
|
|
||||||
identifierNode,
|
|
||||||
componentName.replace("Effect", ""),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
ArrowFunctionExpression: checkThatNodeIsEffectComponent,
|
|
||||||
FunctionDeclaration: checkThatNodeIsEffectComponent,
|
|
||||||
FunctionExpression: checkThatNodeIsEffectComponent,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
name: "effect-components",
|
|
||||||
meta: {
|
|
||||||
docs: {
|
|
||||||
description:
|
|
||||||
"Effect components should end with the Effect suffix. This rule checks only components that are in PascalCase and that return a JSX fragment or null. Any renderProps or camelCase components are ignored.",
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
effectSuffix:
|
|
||||||
"Effect component {{ componentName }} should end with the Effect suffix.",
|
|
||||||
noEffectSuffix:
|
|
||||||
"Component {{ componentName }} shouldn't end with the Effect suffix because it doesn't return a JSX fragment or null.",
|
|
||||||
},
|
|
||||||
type: "suggestion",
|
|
||||||
schema: [],
|
|
||||||
fixable: "code",
|
|
||||||
},
|
|
||||||
defaultOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = effectComponentsRule;
|
|
||||||
|
|
||||||
export default effectComponentsRule;
|
|
@ -1,68 +0,0 @@
|
|||||||
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
||||||
|
|
||||||
const createRule = ESLintUtils.RuleCreator(() => `https://docs.twenty.com`);
|
|
||||||
|
|
||||||
const noStateUseRef = createRule({
|
|
||||||
create: (context) => {
|
|
||||||
return {
|
|
||||||
CallExpression: (node) => {
|
|
||||||
if (
|
|
||||||
node.callee.type !== "Identifier" ||
|
|
||||||
node.callee.name !== "useRef"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node.typeArguments || !node.typeArguments.params?.length) {
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "noStateUseRef",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const typeParam = node.typeArguments.params[0];
|
|
||||||
|
|
||||||
if (typeParam.type !== "TSTypeReference") {
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "noStateUseRef",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeParam.typeName.type !== "Identifier") {
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "noStateUseRef",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!typeParam.typeName.name.match(/^(HTML.*Element|Element)$/)) {
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "test",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
name: "no-state-useref",
|
|
||||||
meta: {
|
|
||||||
docs: {
|
|
||||||
description: "Don't use useRef for state management",
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
test: "test",
|
|
||||||
noStateUseRef:
|
|
||||||
"Don't use useRef for state management. See https://docs.twenty.com/developer/frontend/best-practices#do-not-use-useref-to-store-state for more details.",
|
|
||||||
},
|
|
||||||
type: "suggestion",
|
|
||||||
schema: [],
|
|
||||||
},
|
|
||||||
defaultOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = noStateUseRef;
|
|
||||||
|
|
||||||
export default noStateUseRef;
|
|
@ -1,298 +0,0 @@
|
|||||||
import { TSESTree } from "@typescript-eslint/utils";
|
|
||||||
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
||||||
import {
|
|
||||||
RuleFix,
|
|
||||||
RuleFixer,
|
|
||||||
SourceCode,
|
|
||||||
} from "@typescript-eslint/utils/ts-eslint";
|
|
||||||
|
|
||||||
import postcss from "postcss";
|
|
||||||
|
|
||||||
const createRule = ESLintUtils.RuleCreator(() => `https://docs.twenty.com`);
|
|
||||||
|
|
||||||
interface loc {
|
|
||||||
start: {
|
|
||||||
line: number;
|
|
||||||
column: number;
|
|
||||||
};
|
|
||||||
end: {
|
|
||||||
line: number;
|
|
||||||
column: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isStyledTagname = (node: TSESTree.TaggedTemplateExpression): boolean => {
|
|
||||||
const isMemberExpression = (
|
|
||||||
node: TSESTree.Node,
|
|
||||||
): node is TSESTree.MemberExpression =>
|
|
||||||
node.type === TSESTree.AST_NODE_TYPES.MemberExpression;
|
|
||||||
const isCallExpression = (
|
|
||||||
node: TSESTree.Node,
|
|
||||||
): node is TSESTree.CallExpression =>
|
|
||||||
node.type === TSESTree.AST_NODE_TYPES.CallExpression;
|
|
||||||
const isIdentifier = (
|
|
||||||
node: TSESTree.Node | null,
|
|
||||||
): node is TSESTree.Identifier =>
|
|
||||||
node?.type === TSESTree.AST_NODE_TYPES.Identifier;
|
|
||||||
|
|
||||||
if (isIdentifier(node.tag)) {
|
|
||||||
return node.tag.name === "css";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMemberExpression(node.tag) && isIdentifier(node.tag.object)) {
|
|
||||||
return node.tag.object.name === "styled";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCallExpression(node.tag) && isIdentifier(node.tag.callee)) {
|
|
||||||
return node.tag.callee.name === "styled";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isCallExpression(node.tag) &&
|
|
||||||
isMemberExpression(node.tag.callee) &&
|
|
||||||
isIdentifier(node.tag.callee.object)
|
|
||||||
) {
|
|
||||||
return node.tag.callee.object.name === "styled";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isCallExpression(node.tag) &&
|
|
||||||
isMemberExpression(node.tag.callee) &&
|
|
||||||
isIdentifier(node.tag.callee.object)
|
|
||||||
) {
|
|
||||||
return node.tag.callee.object.name === "styled";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isCallExpression(node.tag) &&
|
|
||||||
isMemberExpression(node.tag.callee) &&
|
|
||||||
isMemberExpression(node.tag.callee.object) &&
|
|
||||||
isIdentifier(node.tag.callee.object.object)
|
|
||||||
) {
|
|
||||||
return node.tag.callee.object.object.name === "styled";
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An atomic rule is a rule without nested rules.
|
|
||||||
*/
|
|
||||||
const isValidAtomicRule = (
|
|
||||||
rule: postcss.Rule,
|
|
||||||
): { isValid: boolean; loc?: loc } => {
|
|
||||||
const decls = rule.nodes.filter(
|
|
||||||
(node) => node.type === "decl",
|
|
||||||
) as unknown as postcss.Declaration[];
|
|
||||||
if (decls.length < 0) {
|
|
||||||
return { isValid: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 1; i < decls.length; i++) {
|
|
||||||
const current = decls[i].prop;
|
|
||||||
const prev = decls[i - 1].prop;
|
|
||||||
if (current < prev) {
|
|
||||||
const loc = {
|
|
||||||
start: {
|
|
||||||
line: decls[i - 1].source!.start!.line,
|
|
||||||
column: decls[i - 1].source!.start!.column - 1,
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
line: decls[i].source!.end!.line,
|
|
||||||
column: decls[i].source!.end!.column - 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return { isValid: false, loc };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isValid: true };
|
|
||||||
};
|
|
||||||
|
|
||||||
const isValidRule = (rule: postcss.Rule): { isValid: boolean; loc?: loc } => {
|
|
||||||
// check each rule recursively
|
|
||||||
const { isValid, loc } = rule.nodes.reduce<{ isValid: boolean; loc?: loc }>(
|
|
||||||
(map, node) => {
|
|
||||||
return node.type === "rule" ? isValidRule(node) : map;
|
|
||||||
},
|
|
||||||
{ isValid: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// if there is any invalid rule, return result
|
|
||||||
if (!isValid) {
|
|
||||||
return { isValid, loc };
|
|
||||||
}
|
|
||||||
|
|
||||||
// check declarations
|
|
||||||
return isValidAtomicRule(rule);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNodeStyles = (node: TSESTree.TaggedTemplateExpression): string => {
|
|
||||||
const [firstQuasi, ...quasis] = node.quasi.quasis;
|
|
||||||
// remove line break added to the first quasi
|
|
||||||
const lineBreakCount = node.quasi.loc.start.line - 1;
|
|
||||||
let styles = `${"\n".repeat(lineBreakCount)}${" ".repeat(
|
|
||||||
node.quasi.loc.start.column + 1,
|
|
||||||
)}${firstQuasi.value.raw}`;
|
|
||||||
|
|
||||||
// replace expression by spaces and line breaks
|
|
||||||
quasis.forEach(({ value, loc }, idx) => {
|
|
||||||
const prevLoc = idx === 0 ? firstQuasi.loc : quasis[idx - 1].loc;
|
|
||||||
const lineBreaksCount = loc.start.line - prevLoc.end.line;
|
|
||||||
const spacesCount =
|
|
||||||
loc.start.line === prevLoc.end.line
|
|
||||||
? loc.start.column - prevLoc.end.column + 2
|
|
||||||
: loc.start.column + 1;
|
|
||||||
styles = `${styles}${" "}${"\n".repeat(lineBreaksCount)}${" ".repeat(
|
|
||||||
spacesCount,
|
|
||||||
)}${value.raw}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return styles;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fix = ({
|
|
||||||
rule,
|
|
||||||
fixer,
|
|
||||||
src,
|
|
||||||
}: {
|
|
||||||
rule: postcss.Rule;
|
|
||||||
fixer: RuleFixer;
|
|
||||||
src: SourceCode;
|
|
||||||
}): RuleFix[] => {
|
|
||||||
let fixings: RuleFix[] = [];
|
|
||||||
|
|
||||||
// concat fixings recursively
|
|
||||||
rule.nodes.forEach((node) => {
|
|
||||||
if (node.type === "rule") {
|
|
||||||
fixings = [...fixings, ...fix({ rule: node, fixer, src })];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const declarations = rule.nodes.filter(
|
|
||||||
(node) => node.type === "decl",
|
|
||||||
) as unknown as postcss.Declaration[];
|
|
||||||
const sortedDeclarations = sortDeclarations(declarations);
|
|
||||||
|
|
||||||
declarations.forEach((decl, idx) => {
|
|
||||||
if (!areSameDeclarations(decl, sortedDeclarations[idx])) {
|
|
||||||
try {
|
|
||||||
const range = getDeclRange({ decl, src });
|
|
||||||
const sortedDeclText = getDeclText({
|
|
||||||
decl: sortedDeclarations[idx],
|
|
||||||
src,
|
|
||||||
});
|
|
||||||
|
|
||||||
fixings.push(fixer.removeRange([range.startIdx, range.endIdx + 1]));
|
|
||||||
fixings.push(
|
|
||||||
fixer.insertTextAfterRange(
|
|
||||||
[range.startIdx, range.startIdx],
|
|
||||||
sortedDeclText,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return fixings;
|
|
||||||
};
|
|
||||||
|
|
||||||
const areSameDeclarations = (
|
|
||||||
a: postcss.ChildNode,
|
|
||||||
b: postcss.ChildNode,
|
|
||||||
): boolean =>
|
|
||||||
a.source!.start!.line === b.source!.start!.line &&
|
|
||||||
a.source!.start!.column === b.source!.start!.column;
|
|
||||||
|
|
||||||
const getDeclRange = ({
|
|
||||||
decl,
|
|
||||||
src,
|
|
||||||
}: {
|
|
||||||
decl: postcss.ChildNode;
|
|
||||||
src: SourceCode;
|
|
||||||
}): { startIdx: number; endIdx: number } => {
|
|
||||||
const loc = {
|
|
||||||
start: {
|
|
||||||
line: decl.source!.start!.line,
|
|
||||||
column: decl.source!.start!.column - 1,
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
line: decl.source!.end!.line,
|
|
||||||
column: decl.source!.end!.column - 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const startIdx = src.getIndexFromLoc(loc.start);
|
|
||||||
const endIdx = src.getIndexFromLoc(loc.end);
|
|
||||||
return { startIdx, endIdx };
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDeclText = ({
|
|
||||||
decl,
|
|
||||||
src,
|
|
||||||
}: {
|
|
||||||
decl: postcss.ChildNode;
|
|
||||||
src: SourceCode;
|
|
||||||
}) => {
|
|
||||||
const { startIdx, endIdx } = getDeclRange({ decl, src });
|
|
||||||
return src.getText().substring(startIdx, endIdx + 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortDeclarations = (declarations: postcss.Declaration[]) =>
|
|
||||||
declarations
|
|
||||||
.slice()
|
|
||||||
.sort((declA, declB) => (declA.prop > declB.prop ? 1 : -1));
|
|
||||||
|
|
||||||
const sortCssPropertiesAlphabeticallyRule = createRule({
|
|
||||||
create: (context) => {
|
|
||||||
return {
|
|
||||||
TaggedTemplateExpression: (node: TSESTree.TaggedTemplateExpression) => {
|
|
||||||
if (isStyledTagname(node)) {
|
|
||||||
try {
|
|
||||||
const root = postcss.parse(
|
|
||||||
getNodeStyles(node),
|
|
||||||
) as unknown as postcss.Rule;
|
|
||||||
|
|
||||||
const { isValid } = isValidRule(root);
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
return context.report({
|
|
||||||
node,
|
|
||||||
messageId: "sortCssPropertiesAlphabetically",
|
|
||||||
fix: (fixer) =>
|
|
||||||
fix({
|
|
||||||
rule: root,
|
|
||||||
fixer,
|
|
||||||
src: context.getSourceCode(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
name: "sort-css-properties-alphabetically",
|
|
||||||
meta: {
|
|
||||||
docs: {
|
|
||||||
description: "Styles are sorted alphabetically.",
|
|
||||||
recommended: "recommended",
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
sortCssPropertiesAlphabetically:
|
|
||||||
"Declarations should be sorted alphabetically.",
|
|
||||||
},
|
|
||||||
type: "suggestion",
|
|
||||||
schema: [],
|
|
||||||
fixable: "code",
|
|
||||||
},
|
|
||||||
defaultOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = sortCssPropertiesAlphabeticallyRule;
|
|
||||||
|
|
||||||
export default sortCssPropertiesAlphabeticallyRule;
|
|
@ -1,62 +0,0 @@
|
|||||||
import {
|
|
||||||
AST_NODE_TYPES,
|
|
||||||
ESLintUtils,
|
|
||||||
TSESTree,
|
|
||||||
} from "@typescript-eslint/utils";
|
|
||||||
|
|
||||||
const createRule = ESLintUtils.RuleCreator(() => `https://docs.twenty.com`);
|
|
||||||
|
|
||||||
const styledComponentsPrefixedWithStyledRule = createRule({
|
|
||||||
create: (context) => {
|
|
||||||
return {
|
|
||||||
VariableDeclarator: (node: TSESTree.VariableDeclarator) => {
|
|
||||||
const templateExpr = node.init;
|
|
||||||
if (templateExpr?.type !== AST_NODE_TYPES.TaggedTemplateExpression) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tag = templateExpr.tag;
|
|
||||||
const tagged =
|
|
||||||
tag.type === AST_NODE_TYPES.MemberExpression
|
|
||||||
? tag.object
|
|
||||||
: tag.type === AST_NODE_TYPES.CallExpression
|
|
||||||
? tag.callee
|
|
||||||
: null;
|
|
||||||
if (
|
|
||||||
tagged?.type === AST_NODE_TYPES.Identifier &&
|
|
||||||
tagged.name === "styled"
|
|
||||||
) {
|
|
||||||
const variable = node.id as TSESTree.Identifier;
|
|
||||||
if (variable.name.startsWith("Styled")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
context.report({
|
|
||||||
node,
|
|
||||||
messageId: "noStyledPrefix",
|
|
||||||
data: {
|
|
||||||
componentName: variable.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
name: "styled-components-prefixed-with-styled",
|
|
||||||
meta: {
|
|
||||||
type: "suggestion",
|
|
||||||
docs: {
|
|
||||||
description: "Warn when StyledComponents are not prefixed with Styled",
|
|
||||||
recommended: "recommended",
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
noStyledPrefix:
|
|
||||||
"{{componentName}} is a StyledComponent and is not prefixed with Styled.",
|
|
||||||
},
|
|
||||||
fixable: "code",
|
|
||||||
schema: [],
|
|
||||||
},
|
|
||||||
defaultOptions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = styledComponentsPrefixedWithStyledRule;
|
|
||||||
|
|
||||||
export default styledComponentsPrefixedWithStyledRule;
|
|
@ -1,47 +0,0 @@
|
|||||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
|
||||||
|
|
||||||
import componentPropsNamingRule from "../rules/component-props-naming";
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run("component-props-naming", componentPropsNamingRule, {
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "export const MyComponent= (props: MyComponentProps) => <div>{props.message}</div>;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "export const MyComponent = ({ message }: MyComponentProps) => <div>{message}</div>;",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "export const MyComponent = (props: OwnProps) => <div>{props.message}</div>;",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidPropsTypeName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output:
|
|
||||||
"export const MyComponent = (props: MyComponentProps) => <div>{props.message}</div>;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "export const MyComponent = ({ message }: OwnProps) => <div>{message}</div>;",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidPropsTypeName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output:
|
|
||||||
"export const MyComponent = ({ message }: MyComponentProps) => <div>{message}</div>;",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
// Required by typescript-eslint https://typescript-eslint.io/packages/rule-tester#type-aware-testing
|
|
@ -1,185 +0,0 @@
|
|||||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
|
||||||
|
|
||||||
import matchingStateVariableRule from "../rules/matching-state-variable";
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run("matching-state-variable", matchingStateVariableRule, {
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "const variable = useRecoilValue(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const variable = useRecoilScopedValue(variableScopedState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [variable, setVariable] = useRecoilState(variableScopedState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [variable, setVariable] = useRecoilScopedState(variableScopedState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [variable, setVariable] = useRecoilFamilyState(variableScopedState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [variable, setVariable] = useRecoilScopedFamilyState(variableScopedState);",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "const myValue = useRecoilValue(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const variable = useRecoilValue(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const myValue = useRecoilScopedValue(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const variable = useRecoilScopedValue(variableState);",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
code: "const [myValue, setMyValue] = useRecoilState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [variable, setVariable] = useRecoilState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [myValue] = useRecoilState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [variable] = useRecoilState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [, setMyValue] = useRecoilState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [, setVariable] = useRecoilState(variableState);",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
code: "const [myValue, setMyValue] = useRecoilScopedState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output:
|
|
||||||
"const [variable, setVariable] = useRecoilScopedState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [myValue] = useRecoilScopedState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [variable] = useRecoilScopedState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [, setMyValue] = useRecoilScopedState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [, setVariable] = useRecoilScopedState(variableState);",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
code: "const [myValue, setMyValue] = useRecoilFamilyState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output:
|
|
||||||
"const [variable, setVariable] = useRecoilFamilyState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [myValue] = useRecoilFamilyState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [variable] = useRecoilFamilyState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [, setMyValue] = useRecoilFamilyState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [, setVariable] = useRecoilFamilyState(variableState);",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
code: "const [myValue, setMyValue] = useRecoilScopedFamilyState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output:
|
|
||||||
"const [variable, setVariable] = useRecoilScopedFamilyState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [myValue] = useRecoilScopedFamilyState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidVariableName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output: "const [variable] = useRecoilScopedFamilyState(variableState);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const [, setMyValue] = useRecoilScopedFamilyState(variableState);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "invalidSetterName",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
output:
|
|
||||||
"const [, setVariable] = useRecoilScopedFamilyState(variableState);",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,62 +0,0 @@
|
|||||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
|
||||||
|
|
||||||
import noHardcodedColorsRule from "../rules/no-hardcoded-colors";
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run("no-hardcoded-colors", noHardcodedColorsRule, {
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "const color = theme.background.secondary;",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: 'const color = "rgb(154,205,50)";',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "hardcodedColor",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'const color = { test: "rgb(154,205,50)", test2: "#ADFF2F" }',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "hardcodedColor",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
messageId: "hardcodedColor",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const color = { test: `rgb(${r},${g},${b})`, test2: `#ADFF${test}` }",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "hardcodedColor",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
messageId: "hardcodedColor",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'const color = "#ADFF2F";',
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "hardcodedColor",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,51 +0,0 @@
|
|||||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
|
||||||
|
|
||||||
import noStateUseRefRule from "../rules/no-state-useref";
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run("no-state-useref", noStateUseRefRule, {
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "const scrollableRef = useRef<HTMLDivElement>(null);",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const ref = useRef<HTMLInputElement>(null);",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "const ref = useRef(null);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "noStateUseRef",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const ref = useRef<Boolean>(null);",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "noStateUseRef",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const ref = useRef<string>('');",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "noStateUseRef",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
// Required by typescript-eslint https://typescript-eslint.io/packages/rule-tester#type-aware-testing
|
|
@ -1,56 +0,0 @@
|
|||||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
|
||||||
|
|
||||||
import sortCssPropertiesAlphabeticallyRule from "../rules/sort-css-properties-alphabetically";
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run(
|
|
||||||
"sort-css-properties-alphabetically",
|
|
||||||
sortCssPropertiesAlphabeticallyRule,
|
|
||||||
{
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "const style = css`color: red;`;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const style = css`background-color: $bgColor;color: red;`;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const StyledComponent = styled.div`color: red;`;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const StyledComponent = styled.div`background-color: $bgColor;color: red;`;",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "const style = css`color: #FF0000;background-color: $bgColor`;",
|
|
||||||
output: "const style = css`background-color: $bgColorcolor: #FF0000;`;",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "sortCssPropertiesAlphabetically",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const StyledComponent = styled.div`color: #FF0000;background-color: $bgColor`;",
|
|
||||||
output:
|
|
||||||
"const StyledComponent = styled.div`background-color: $bgColorcolor: #FF0000;`;",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "sortCssPropertiesAlphabetically",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
@ -1,47 +0,0 @@
|
|||||||
import { RuleTester } from "@typescript-eslint/rule-tester";
|
|
||||||
|
|
||||||
import styledComponentsPrefixedWithStyledRule from "../rules/styled-components-prefixed-with-styled";
|
|
||||||
|
|
||||||
const ruleTester = new RuleTester({
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
parserOptions: {
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
ruleTester.run(
|
|
||||||
"styled-components-prefixed-with-styled",
|
|
||||||
styledComponentsPrefixedWithStyledRule,
|
|
||||||
{
|
|
||||||
valid: [
|
|
||||||
{
|
|
||||||
code: "const StyledButton = styled.button``;",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const StyledComponent = styled.div``;",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
invalid: [
|
|
||||||
{
|
|
||||||
code: "const Button = styled.button``;",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "noStyledPrefix",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: "const Component = styled.div``;",
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
messageId: "noStyledPrefix",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
);
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"strict": true
|
|
||||||
},
|
|
||||||
"include": ["./file.ts", "./react.tsx"]
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
|
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
|
||||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and in clude compatible library declarations. */
|
|
||||||
"module": "Node16", /* Specify what module code is generated. */
|
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
|
||||||
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
|
||||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
|
||||||
"moduleResolution": "Node16",
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ services:
|
|||||||
- ../../..:/app
|
- ../../..:/app
|
||||||
- twenty_dev_node_modules_root:/app/node_modules
|
- twenty_dev_node_modules_root:/app/node_modules
|
||||||
- twenty_dev_node_modules_docs:/app/packages/twenty-docs/node_modules
|
- twenty_dev_node_modules_docs:/app/packages/twenty-docs/node_modules
|
||||||
- twenty_dev_node_modules_eslint:/app/packages/eslint-plugin-twenty/node_modules
|
|
||||||
- twenty_dev_node_modules_front:/app/packages/twenty-front/node_modules
|
- twenty_dev_node_modules_front:/app/packages/twenty-front/node_modules
|
||||||
- twenty_dev_node_modules_server:/app/packages/twenty-server/node_modules
|
- twenty_dev_node_modules_server:/app/packages/twenty-server/node_modules
|
||||||
- twenty_dev_node_modules_website:/app/packages/twenty-website/node_modules
|
- twenty_dev_node_modules_website:/app/packages/twenty-website/node_modules
|
||||||
|
@ -10,7 +10,7 @@ COPY ./package.json .
|
|||||||
COPY ./yarn.lock .
|
COPY ./yarn.lock .
|
||||||
COPY ./.yarnrc.yml .
|
COPY ./.yarnrc.yml .
|
||||||
COPY ./.yarn/releases /app/.yarn/releases
|
COPY ./.yarn/releases /app/.yarn/releases
|
||||||
COPY ./packages/eslint-plugin-twenty /app/packages/eslint-plugin-twenty
|
COPY ./tools/eslint-rules /app/tools/eslint-rules
|
||||||
COPY ./packages/twenty-front /app/packages/twenty-front
|
COPY ./packages/twenty-front /app/packages/twenty-front
|
||||||
|
|
||||||
RUN yarn
|
RUN yarn
|
||||||
|
@ -6,7 +6,7 @@ COPY ./package.json .
|
|||||||
COPY ./yarn.lock .
|
COPY ./yarn.lock .
|
||||||
COPY ./.yarnrc.yml .
|
COPY ./.yarnrc.yml .
|
||||||
COPY ./.yarn/releases /app/.yarn/releases
|
COPY ./.yarn/releases /app/.yarn/releases
|
||||||
COPY ./packages/eslint-plugin-twenty /app/packages/eslint-plugin-twenty
|
COPY ./tools/eslint-rules /app/tools/eslint-rules
|
||||||
COPY ./packages/twenty-server /app/packages/twenty-server
|
COPY ./packages/twenty-server /app/packages/twenty-server
|
||||||
RUN yarn
|
RUN yarn
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ twenty
|
|||||||
└───twenty-front // contains the frontend code for the application
|
└───twenty-front // contains the frontend code for the application
|
||||||
└───twenty-server // contains the backend code for the application
|
└───twenty-server // contains the backend code for the application
|
||||||
└───twenty-docker // contains docker configurations for development and production build
|
└───twenty-docker // contains docker configurations for development and production build
|
||||||
└───many other packages your are invited to discover such as twenty-docs, twenty-ui, eslint-plugin-twenty, twenty-zapier...
|
└───many other packages you are invited to discover such as twenty-docs, twenty-ui, eslint-rules, twenty-zapier...
|
||||||
```
|
```
|
||||||
|
|
||||||
## IDE Setup
|
## IDE Setup
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
overrides: [
|
extends: ['./.eslintrc.cjs'],
|
||||||
{
|
|
||||||
files: ['*.stories.tsx', '*.test.ts'],
|
|
||||||
rules: {
|
|
||||||
'no-console': 'off',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
extends: [
|
|
||||||
'./.eslintrc.cjs'
|
|
||||||
],
|
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
}
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['.storybook/**/*', '**/*.stories.tsx', '**/*.test.ts'],
|
||||||
|
rules: {
|
||||||
|
'no-console': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
@ -1,56 +1,26 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parser: '@typescript-eslint/parser',
|
extends: [
|
||||||
root: true,
|
'plugin:@nx/react',
|
||||||
env: {
|
'plugin:react/recommended',
|
||||||
browser: true,
|
'plugin:react-hooks/recommended',
|
||||||
node: true,
|
'plugin:storybook/recommended',
|
||||||
jest: true,
|
'../../.eslintrc.js',
|
||||||
},
|
],
|
||||||
parserOptions: {
|
plugins: ['react-hooks', 'react-refresh'],
|
||||||
ecmaVersion: 'latest',
|
ignorePatterns: [
|
||||||
sourceType: 'module',
|
'!**/*',
|
||||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
'node_modules',
|
||||||
tsconfigRootDir: __dirname,
|
'mockServiceWorker.js',
|
||||||
},
|
'**/generated*/*',
|
||||||
|
'*config.*',
|
||||||
|
'**/*config.js',
|
||||||
|
'codegen*',
|
||||||
|
'tsup.ui.index.tsx',
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'simple-import-sort/imports': 'error',
|
|
||||||
'simple-import-sort/exports': 'error',
|
|
||||||
'twenty/effect-components': 'error',
|
|
||||||
'twenty/no-hardcoded-colors': 'error',
|
|
||||||
'twenty/matching-state-variable': 'error',
|
|
||||||
'twenty/component-props-naming': 'error',
|
|
||||||
'twenty/sort-css-properties-alphabetically': 'error',
|
|
||||||
'twenty/styled-components-prefixed-with-styled': 'error',
|
|
||||||
'twenty/no-state-useref': 'error',
|
|
||||||
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
|
|
||||||
'no-unused-vars': 'off',
|
|
||||||
'react/jsx-props-no-spreading': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
explicitSpread: 'ignore',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'react-hooks/exhaustive-deps': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
additionalHooks: 'useRecoilCallback',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'unused-imports/no-unused-imports': 'warn',
|
|
||||||
'unused-imports/no-unused-vars': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
vars: 'all',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
args: 'after-used',
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-restricted-imports': [
|
'no-restricted-imports': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
@ -67,87 +37,58 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'@typescript-eslint/consistent-type-imports': [
|
|
||||||
|
'@nx/workspace-effect-components': 'error',
|
||||||
|
'@nx/workspace-no-hardcoded-colors': 'error',
|
||||||
|
'@nx/workspace-matching-state-variable': 'error',
|
||||||
|
'@nx/workspace-sort-css-properties-alphabetically': 'error',
|
||||||
|
'@nx/workspace-styled-components-prefixed-with-styled': 'error',
|
||||||
|
'@nx/workspace-no-state-useref': 'error',
|
||||||
|
'@nx/workspace-component-props-naming': 'error',
|
||||||
|
|
||||||
|
'react/no-unescaped-entities': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'react/jsx-key': 'off',
|
||||||
|
'react/display-name': 'off',
|
||||||
|
'react/jsx-uses-react': 'off',
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react/jsx-no-useless-fragment': 'off',
|
||||||
|
'react/jsx-props-no-spreading': [
|
||||||
'error',
|
'error',
|
||||||
{ prefer: 'no-type-imports' },
|
{
|
||||||
|
explicitSpread: 'ignore',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'react-hooks/exhaustive-deps': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
additionalHooks: 'useRecoilCallback',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
|
|
||||||
// 'react-refresh/only-export-components': [
|
|
||||||
// 'warn',
|
|
||||||
// { allowConstantExport: true },
|
|
||||||
// ],
|
|
||||||
},
|
},
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:react/recommended',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
'plugin:storybook/recommended',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
'@typescript-eslint/eslint-plugin',
|
|
||||||
'simple-import-sort',
|
|
||||||
'unused-imports',
|
|
||||||
'prefer-arrow',
|
|
||||||
'twenty',
|
|
||||||
'react-refresh',
|
|
||||||
],
|
|
||||||
ignorePatterns: [
|
|
||||||
'mockServiceWorker.js',
|
|
||||||
'**/generated*/*',
|
|
||||||
'.eslintrc.cjs',
|
|
||||||
'*.config.cjs',
|
|
||||||
'*.config.ts',
|
|
||||||
'*config.js',
|
|
||||||
'codegen*',
|
|
||||||
'tsup.ui.index.tsx'
|
|
||||||
],
|
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['*.stories.tsx', '*.test.ts'],
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
||||||
|
parserOptions: {
|
||||||
|
project: ['packages/twenty-front/tsconfig.*?.json'],
|
||||||
|
},
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['.storybook/main.@(js|cjs|mjs|ts)'],
|
||||||
|
rules: {
|
||||||
|
'storybook/no-uninstalled-addons': [
|
||||||
|
'error',
|
||||||
|
{ packageJsonLocation: path.resolve('../../package.json') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['.storybook/**/*', '**/*.stories.tsx', '**/*.test.@(ts|tsx)'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': 'off',
|
'no-console': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
|
|
||||||
rules: {
|
|
||||||
'react/no-unescaped-entities': 'off',
|
|
||||||
'react/prop-types': 'off',
|
|
||||||
'react/jsx-key': 'off',
|
|
||||||
'react/display-name': 'off',
|
|
||||||
'react/jsx-uses-react': 'off',
|
|
||||||
'react/react-in-jsx-scope': 'off',
|
|
||||||
'no-control-regex': 0,
|
|
||||||
'no-undef': 'off',
|
|
||||||
'simple-import-sort/imports': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
groups: [
|
|
||||||
['^react', '^@?\\w'],
|
|
||||||
['^(@|~)(/.*|$)'],
|
|
||||||
['^\\u0000'],
|
|
||||||
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
|
|
||||||
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
|
||||||
['^.+\\.?(css)$'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'prefer-arrow/prefer-arrow-functions': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
disallowPrototype: true,
|
|
||||||
singleReturnOnly: false,
|
|
||||||
classPropertiesAllowed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
**/generated*/
|
|
||||||
*.lock
|
|
||||||
*.yaml
|
|
@ -1,4 +1,4 @@
|
|||||||
import type { StorybookConfig } from '@storybook/react-vite';
|
import { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
const computeStoriesGlob = () => {
|
const computeStoriesGlob = () => {
|
||||||
if (process.env.STORYBOOK_SCOPE === 'pages') {
|
if (process.env.STORYBOOK_SCOPE === 'pages') {
|
||||||
@ -6,19 +6,22 @@ const computeStoriesGlob = () => {
|
|||||||
'../src/pages/**/*.stories.@(js|jsx|ts|tsx)',
|
'../src/pages/**/*.stories.@(js|jsx|ts|tsx)',
|
||||||
'../src/__stories__/*.stories.@(js|jsx|ts|tsx)',
|
'../src/__stories__/*.stories.@(js|jsx|ts|tsx)',
|
||||||
'../src/pages/**/*.docs.mdx',
|
'../src/pages/**/*.docs.mdx',
|
||||||
'../src/__stories__/*.docs.mdx'
|
'../src/__stories__/*.docs.mdx',
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.STORYBOOK_SCOPE === 'modules') {
|
if (process.env.STORYBOOK_SCOPE === 'modules') {
|
||||||
return ['../src/modules/**/*.stories.@(js|jsx|ts|tsx)', '../src/modules/**/*.docs.mdx']
|
return [
|
||||||
|
'../src/modules/**/*.stories.@(js|jsx|ts|tsx)',
|
||||||
|
'../src/modules/**/*.docs.mdx',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.STORYBOOK_SCOPE === 'ui-docs') {
|
if (process.env.STORYBOOK_SCOPE === 'ui-docs') {
|
||||||
return ['../src/modules/ui/**/*.docs.mdx'];
|
return ['../src/modules/ui/**/*.docs.mdx'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['../src/**/*.docs.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)']
|
return ['../src/**/*.docs.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const config: StorybookConfig = {
|
const config: StorybookConfig = {
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { ThemeProvider } from '@emotion/react';
|
import { ThemeProvider } from '@emotion/react';
|
||||||
|
import { withThemeFromJSXProvider } from '@storybook/addon-themes';
|
||||||
import { Preview, ReactRenderer } from '@storybook/react';
|
import { Preview, ReactRenderer } from '@storybook/react';
|
||||||
import { withThemeFromJSXProvider } from "@storybook/addon-themes";
|
|
||||||
import { initialize, mswDecorator } from 'msw-storybook-addon';
|
import { initialize, mswDecorator } from 'msw-storybook-addon';
|
||||||
|
|
||||||
|
import { darkTheme, lightTheme } from '../src/modules/ui/theme/constants/theme';
|
||||||
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
||||||
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
||||||
|
|
||||||
import { lightTheme, darkTheme } from '../src/modules/ui/theme/constants/theme';
|
|
||||||
import 'react-loading-skeleton/dist/skeleton.css';
|
import 'react-loading-skeleton/dist/skeleton.css';
|
||||||
|
|
||||||
initialize({
|
initialize({
|
||||||
onUnhandledRequest: async (request: Request) => {
|
onUnhandledRequest: async (request: Request) => {
|
||||||
const fileExtensionsToIgnore = /\.(ts|tsx|js|jsx|svg|css|png)(\?v=[a-zA-Z0-9]+)?/;
|
const fileExtensionsToIgnore =
|
||||||
|
/\.(ts|tsx|js|jsx|svg|css|png)(\?v=[a-zA-Z0-9]+)?/;
|
||||||
|
|
||||||
if (fileExtensionsToIgnore.test(request.url)) {
|
if (fileExtensionsToIgnore.test(request.url)) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getJestConfig } from "@storybook/test-runner";
|
import { getJestConfig } from '@storybook/test-runner';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@jest/types').Config.InitialOptions}
|
* @type {import('@jest/types').Config.InitialOptions}
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
"tsc": "tsc --watch",
|
"tsc": "tsc --watch",
|
||||||
"tsc:ci": "tsc",
|
"tsc:ci": "tsc",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --report-unused-disable-directives --max-warnings 0 --config .eslintrc.cjs",
|
||||||
"lint:ci": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --config .eslintrc-ci.cjs",
|
"lint:ci": "yarn lint --config .eslintrc-ci.cjs",
|
||||||
"fmt:fix": "prettier --cache --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
"fmt:fix": "prettier --cache --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||||
"fmt": "prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"",
|
"fmt": "prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
@ -49,7 +49,6 @@
|
|||||||
"@hookform/resolvers": "^3.1.1",
|
"@hookform/resolvers": "^3.1.1",
|
||||||
"@sentry/react": "^7.88.0",
|
"@sentry/react": "^7.88.0",
|
||||||
"@sniptt/guards": "^0.2.0",
|
"@sniptt/guards": "^0.2.0",
|
||||||
"@swc/core": "^1.3.100",
|
|
||||||
"@swc/jest": "^0.2.29",
|
"@swc/jest": "^0.2.29",
|
||||||
"@tabler/icons-react": "^2.30.0",
|
"@tabler/icons-react": "^2.30.0",
|
||||||
"afterframe": "^1.0.2",
|
"afterframe": "^1.0.2",
|
||||||
@ -69,7 +68,6 @@
|
|||||||
"lodash.kebabcase": "^4.1.1",
|
"lodash.kebabcase": "^4.1.1",
|
||||||
"lodash.snakecase": "^4.1.1",
|
"lodash.snakecase": "^4.1.1",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"nx": "17.2.3",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-data-grid": "7.0.0-beta.13",
|
"react-data-grid": "7.0.0-beta.13",
|
||||||
"react-datepicker": "^4.11.0",
|
"react-datepicker": "^4.11.0",
|
||||||
@ -96,72 +94,6 @@
|
|||||||
"xlsx-ugnis": "^0.19.3",
|
"xlsx-ugnis": "^0.19.3",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
|
||||||
"@graphql-codegen/cli": "^3.3.1",
|
|
||||||
"@graphql-codegen/client-preset": "^4.1.0",
|
|
||||||
"@graphql-codegen/typescript": "^3.0.4",
|
|
||||||
"@graphql-codegen/typescript-operations": "^3.0.4",
|
|
||||||
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
|
||||||
"@storybook/addon-actions": "^7.6.3",
|
|
||||||
"@storybook/addon-coverage": "^1.0.0",
|
|
||||||
"@storybook/addon-essentials": "^7.6.3",
|
|
||||||
"@storybook/addon-interactions": "^7.6.3",
|
|
||||||
"@storybook/addon-links": "^7.6.3",
|
|
||||||
"@storybook/addon-onboarding": "^1.0.9",
|
|
||||||
"@storybook/addon-themes": "^7.6.3",
|
|
||||||
"@storybook/blocks": "^7.6.3",
|
|
||||||
"@storybook/react": "^7.6.3",
|
|
||||||
"@storybook/react-vite": "^7.6.3",
|
|
||||||
"@storybook/test": "^7.6.3",
|
|
||||||
"@storybook/test-runner": "^0.16.0",
|
|
||||||
"@testing-library/jest-dom": "^6.1.5",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@types/apollo-upload-client": "^17.0.2",
|
|
||||||
"@types/deep-equal": "^1.0.1",
|
|
||||||
"@types/jest": "^29.5.10",
|
|
||||||
"@types/js-cookie": "^3.0.3",
|
|
||||||
"@types/lodash.camelcase": "^4.3.7",
|
|
||||||
"@types/lodash.debounce": "^4.0.7",
|
|
||||||
"@types/lodash.kebabcase": "^4.1.7",
|
|
||||||
"@types/lodash.snakecase": "^4.1.9",
|
|
||||||
"@types/luxon": "^3.3.0",
|
|
||||||
"@types/node": "^20.10.0",
|
|
||||||
"@types/react": "^18.2.39",
|
|
||||||
"@types/react-datepicker": "^4.11.2",
|
|
||||||
"@types/react-dom": "^18.2.15",
|
|
||||||
"@types/scroll-into-view": "^1.16.0",
|
|
||||||
"@types/uuid": "^9.0.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
|
||||||
"@typescript-eslint/parser": "^6.10.0",
|
|
||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
|
||||||
"chromatic": "^6.18.0",
|
|
||||||
"concurrently": "^8.0.1",
|
|
||||||
"cross-var": "^1.1.0",
|
|
||||||
"dotenv-cli": "^7.2.1",
|
|
||||||
"eslint": "^8.53.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
||||||
"eslint-plugin-storybook": "^0.6.15",
|
|
||||||
"eslint-plugin-twenty": "file:../eslint-plugin-twenty/eslint-plugin-twenty.tgz",
|
|
||||||
"eslint-plugin-unused-imports": "^3.0.0",
|
|
||||||
"http-server": "^14.1.1",
|
|
||||||
"jest": "29.7.0",
|
|
||||||
"jest-environment-jsdom": "29.7.0",
|
|
||||||
"msw": "^2.0.11",
|
|
||||||
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
|
|
||||||
"prettier": "^3.1.0",
|
|
||||||
"storybook": "^7.6.3",
|
|
||||||
"storybook-addon-cookie": "^3.1.0",
|
|
||||||
"storybook-addon-pseudo-states": "^2.1.2",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"vite": "^5.0.0",
|
|
||||||
"vite-plugin-svgr": "^4.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.16.0",
|
"node": "^18.16.0",
|
||||||
"npm": "please-use-yarn",
|
"npm": "please-use-yarn",
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
window._env_ = {
|
window._env_ = {
|
||||||
// This file should stay empty. It will be overwritten by the build process.
|
// This file should stay empty. It will be overwritten by the build process.
|
||||||
}
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
export const useFirstMountState = (): boolean => {
|
export const useFirstMountState = (): boolean => {
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const isFirst = useRef(true);
|
const isFirst = useRef(true);
|
||||||
|
|
||||||
if (isFirst.current) {
|
if (isFirst.current) {
|
||||||
|
@ -55,7 +55,7 @@ export const ActivityBodyEditor = ({
|
|||||||
const imagesActivated = useIsFeatureEnabled('IS_NOTE_CREATE_IMAGES_ENABLED');
|
const imagesActivated = useIsFeatureEnabled('IS_NOTE_CREATE_IMAGES_ENABLED');
|
||||||
|
|
||||||
if (!imagesActivated) {
|
if (!imagesActivated) {
|
||||||
slashMenuItems = slashMenuItems.filter((x) => x.name != 'Image');
|
slashMenuItems = slashMenuItems.filter((x) => x.name !== 'Image');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [uploadFile] = useUploadFileMutation();
|
const [uploadFile] = useUploadFileMutation();
|
||||||
|
@ -102,8 +102,8 @@ export const Attachments = ({
|
|||||||
fullPath: attachmentUrl,
|
fullPath: attachmentUrl,
|
||||||
type: getFileType(file.name),
|
type: getFileType(file.name),
|
||||||
companyId:
|
companyId:
|
||||||
targetableEntity.type == 'Company' ? targetableEntity.id : null,
|
targetableEntity.type === 'Company' ? targetableEntity.id : null,
|
||||||
personId: targetableEntity.type == 'Person' ? targetableEntity.id : null,
|
personId: targetableEntity.type === 'Person' ? targetableEntity.id : null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ export const useHandleCheckableActivityTargetChange = ({
|
|||||||
.map(([id, _]) => id);
|
.map(([id, _]) => id);
|
||||||
|
|
||||||
if (idsToAdd.length) {
|
if (idsToAdd.length) {
|
||||||
idsToAdd.map((id) => {
|
idsToAdd.forEach((id) => {
|
||||||
const entityFromToSelect = entitiesToSelect.filter(
|
const entityFromToSelect = entitiesToSelect.filter(
|
||||||
(entity: any) => entity.id === id,
|
(entity: any) => entity.id === id,
|
||||||
).length
|
).length
|
||||||
@ -65,7 +65,7 @@ export const useHandleCheckableActivityTargetChange = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (idsToDelete.length) {
|
if (idsToDelete.length) {
|
||||||
idsToDelete.map((id) => {
|
idsToDelete.forEach((id) => {
|
||||||
const currentActivityTargetId = currentActivityTargets.filter(
|
const currentActivityTargetId = currentActivityTargets.filter(
|
||||||
({ companyId, personId }) => companyId === id || personId === id,
|
({ companyId, personId }) => companyId === id || personId === id,
|
||||||
)[0].id;
|
)[0].id;
|
||||||
|
@ -62,7 +62,7 @@ export const NoteList = ({ title, notes, button }: NoteListProps) => (
|
|||||||
<NoteCard
|
<NoteCard
|
||||||
key={note.id}
|
key={note.id}
|
||||||
note={note}
|
note={note}
|
||||||
isSingleNote={notes.length == 1}
|
isSingleNote={notes.length === 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</StyledNoteContainer>
|
</StyledNoteContainer>
|
||||||
|
@ -13,7 +13,7 @@ import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
|||||||
import { ApolloFactory } from '../services/apollo.factory';
|
import { ApolloFactory } from '../services/apollo.factory';
|
||||||
|
|
||||||
export const useApolloFactory = () => {
|
export const useApolloFactory = () => {
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
|
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
|
||||||
const [isDebugMode] = useRecoilState(isDebugModeState);
|
const [isDebugMode] = useRecoilState(isDebugModeState);
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { OperationType } from '../types/operation-type';
|
import { OperationType } from '../types/operation-type';
|
||||||
|
|
||||||
const operationTypeColors = {
|
const operationTypeColors = {
|
||||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
query: '#03A9F4',
|
query: '#03A9F4',
|
||||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
mutation: '#61A600',
|
mutation: '#61A600',
|
||||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
subscription: '#61A600',
|
subscription: '#61A600',
|
||||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
error: '#F51818',
|
error: '#F51818',
|
||||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
default: '#61A600',
|
default: '#61A600',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ export const loggerLink = (getSchemaName: (operation: Operation) => string) =>
|
|||||||
errors.forEach((err: any) => {
|
errors.forEach((err: any) => {
|
||||||
logDebug(
|
logDebug(
|
||||||
`%c${err.message}`,
|
`%c${err.message}`,
|
||||||
// eslint-disable-next-line twenty/no-hardcoded-colors
|
// eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||||
'color: #F51818; font-weight: lighter',
|
'color: #F51818; font-weight: lighter',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -157,6 +157,7 @@ export const useAuth = () => {
|
|||||||
set(supportChatState, supportChat);
|
set(supportChatState, supportChat);
|
||||||
set(telemetryState, telemetry);
|
set(telemetryState, telemetry);
|
||||||
set(isDebugModeState, isDebugMode);
|
set(isDebugModeState, isDebugMode);
|
||||||
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
goToRecoilSnapshot(initialSnapshot);
|
goToRecoilSnapshot(initialSnapshot);
|
||||||
|
@ -44,7 +44,7 @@ export const ObjectMetadataNavItems = () => {
|
|||||||
key={objectMetadataItem.id}
|
key={objectMetadataItem.id}
|
||||||
label={objectMetadataItem.labelPlural}
|
label={objectMetadataItem.labelPlural}
|
||||||
to={`/objects/${objectMetadataItem.namePlural}`}
|
to={`/objects/${objectMetadataItem.namePlural}`}
|
||||||
active={currentPath == `/objects/${objectMetadataItem.namePlural}`}
|
active={currentPath === `/objects/${objectMetadataItem.namePlural}`}
|
||||||
Icon={getIcon(objectMetadataItem.icon)}
|
Icon={getIcon(objectMetadataItem.icon)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/objects/${objectMetadataItem.namePlural}`);
|
navigate(`/objects/${objectMetadataItem.namePlural}`);
|
||||||
|
@ -36,7 +36,7 @@ const StyledIconTableCell = styled(TableCell)`
|
|||||||
|
|
||||||
export const SettingsObjectFieldItemTableRow = ({
|
export const SettingsObjectFieldItemTableRow = ({
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
fieldMetadataItem: fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
}: SettingsObjectFieldItemTableRowProps) => {
|
}: SettingsObjectFieldItemTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
|
import { ModalWrapper } from '@/spreadsheet-import/components/ModalWrapper';
|
||||||
import { Providers } from '@/spreadsheet-import/components/Providers';
|
import { Providers } from '@/spreadsheet-import/components/Providers';
|
||||||
import { Steps } from '@/spreadsheet-import/steps/components/Steps';
|
import { Steps } from '@/spreadsheet-import/steps/components/Steps';
|
||||||
import { SpreadsheetOptions } from '@/spreadsheet-import/types';
|
import { SpreadsheetOptions as SpreadsheetImportProps } from '@/spreadsheet-import/types';
|
||||||
|
|
||||||
export const defaultSpreadsheetImportProps: Partial<SpreadsheetOptions<any>> = {
|
export const defaultSpreadsheetImportProps: Partial<
|
||||||
|
SpreadsheetImportProps<any>
|
||||||
|
> = {
|
||||||
autoMapHeaders: true,
|
autoMapHeaders: true,
|
||||||
allowInvalidSubmit: true,
|
allowInvalidSubmit: true,
|
||||||
autoMapDistance: 2,
|
autoMapDistance: 2,
|
||||||
@ -17,8 +19,7 @@ export const defaultSpreadsheetImportProps: Partial<SpreadsheetOptions<any>> = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const SpreadsheetImport = <T extends string>(
|
export const SpreadsheetImport = <T extends string>(
|
||||||
// eslint-disable-next-line twenty/component-props-naming
|
props: SpreadsheetImportProps<T>,
|
||||||
props: SpreadsheetOptions<T>,
|
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<Providers values={props}>
|
<Providers values={props}>
|
||||||
|
@ -56,9 +56,9 @@ export const ProgressBar = forwardRef<ProgressBarControls, ProgressBarProps>(
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const controls = useAnimation();
|
const controls = useAnimation();
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const startTimestamp = useRef<number>(0);
|
const startTimestamp = useRef<number>(0);
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const remainingTime = useRef<number>(duration);
|
const remainingTime = useRef<number>(duration);
|
||||||
|
|
||||||
const start = useCallback(async () => {
|
const start = useCallback(async () => {
|
||||||
|
@ -118,7 +118,7 @@ export const SnackBar = ({
|
|||||||
}: SnackBarProps) => {
|
}: SnackBarProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const progressBarRef = useRef<ProgressBarControls | null>(null);
|
const progressBarRef = useRef<ProgressBarControls | null>(null);
|
||||||
|
|
||||||
const closeSnackbar = useCallback(() => {
|
const closeSnackbar = useCallback(() => {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
export const usePausableTimeout = (callback: () => void, delay: number) => {
|
export const usePausableTimeout = (callback: () => void, delay: number) => {
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const savedCallback = useRef<() => void>(callback);
|
const savedCallback = useRef<() => void>(callback);
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const remainingTime = useRef<number>(delay);
|
const remainingTime = useRef<number>(delay);
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const startTime = useRef<number>(Date.now());
|
const startTime = useRef<number>(Date.now());
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const timeoutId = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
|
@ -131,7 +131,7 @@ const TextInputComponent = (
|
|||||||
tabIndex,
|
tabIndex,
|
||||||
RightIcon,
|
RightIcon,
|
||||||
}: TextInputComponentProps,
|
}: TextInputComponentProps,
|
||||||
// eslint-disable-next-line twenty/component-props-naming
|
// eslint-disable-next-line @nx/workspace-component-props-naming
|
||||||
ref: ForwardedRef<HTMLInputElement>,
|
ref: ForwardedRef<HTMLInputElement>,
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
@ -23,7 +23,7 @@ const StyledEditor = styled.div`
|
|||||||
|
|
||||||
export const BlockEditor = ({ editor }: BlockEditorProps) => {
|
export const BlockEditor = ({ editor }: BlockEditorProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const blockNoteTheme = theme.name == 'light' ? 'light' : 'dark';
|
const blockNoteTheme = theme.name === 'light' ? 'light' : 'dark';
|
||||||
return (
|
return (
|
||||||
<StyledEditor>
|
<StyledEditor>
|
||||||
<BlockNoteView editor={editor} theme={blockNoteTheme} />
|
<BlockNoteView editor={editor} theme={blockNoteTheme} />
|
||||||
|
@ -30,7 +30,7 @@ export const ShowPageMoreButton = ({
|
|||||||
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
|
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { deleteOneRecord: deleteOneRecord } = useDeleteOneRecord({
|
const { deleteOneRecord } = useDeleteOneRecord({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable twenty/no-hardcoded-colors */
|
/* eslint-disable @nx/workspace-no-hardcoded-colors */
|
||||||
import DarkNoise from '../assets/dark-noise.jpg';
|
import DarkNoise from '../assets/dark-noise.jpg';
|
||||||
import LightNoise from '../assets/light-noise.png';
|
import LightNoise from '../assets/light-noise.png';
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable twenty/no-hardcoded-colors */
|
/* eslint-disable @nx/workspace-no-hardcoded-colors */
|
||||||
import hexRgb from 'hex-rgb';
|
import hexRgb from 'hex-rgb';
|
||||||
|
|
||||||
export const grayScale = {
|
export const grayScale = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable twenty/no-hardcoded-colors */
|
/* eslint-disable @nx/workspace-no-hardcoded-colors */
|
||||||
import { accentDark, accentLight } from './accent';
|
import { accentDark, accentLight } from './accent';
|
||||||
import { animation } from './animation';
|
import { animation } from './animation';
|
||||||
import { backgroundDark, backgroundLight } from './background';
|
import { backgroundDark, backgroundLight } from './background';
|
||||||
|
@ -18,7 +18,7 @@ export const RecoilScope = ({
|
|||||||
scopeId?: string;
|
scopeId?: string;
|
||||||
CustomRecoilScopeContext?: RecoilScopeContextType;
|
CustomRecoilScopeContext?: RecoilScopeContextType;
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line twenty/no-state-useref
|
// eslint-disable-next-line @nx/workspace-no-state-useref
|
||||||
const currentScopeId = useRef(scopeId ?? v4());
|
const currentScopeId = useRef(scopeId ?? v4());
|
||||||
|
|
||||||
return CustomRecoilScopeContext ? (
|
return CustomRecoilScopeContext ? (
|
||||||
|
@ -28,8 +28,8 @@ export const getViewScopedStatesFromSnapshot = ({
|
|||||||
availableFieldDefinitionsState,
|
availableFieldDefinitionsState,
|
||||||
availableFilterDefinitionsState,
|
availableFilterDefinitionsState,
|
||||||
availableSortDefinitionsState,
|
availableSortDefinitionsState,
|
||||||
canPersistFiltersSelector: canPersistFiltersSelector,
|
canPersistFiltersSelector,
|
||||||
canPersistSortsSelector: canPersistSortsSelector,
|
canPersistSortsSelector,
|
||||||
currentViewFieldsState,
|
currentViewFieldsState,
|
||||||
currentViewFiltersState,
|
currentViewFiltersState,
|
||||||
currentViewIdState,
|
currentViewIdState,
|
||||||
@ -41,11 +41,11 @@ export const getViewScopedStatesFromSnapshot = ({
|
|||||||
onViewFieldsChangeState,
|
onViewFieldsChangeState,
|
||||||
onViewFiltersChangeState,
|
onViewFiltersChangeState,
|
||||||
onViewSortsChangeState,
|
onViewSortsChangeState,
|
||||||
savedViewFieldsByKeySelector: savedViewFieldsByKeySelector,
|
savedViewFieldsByKeySelector,
|
||||||
savedViewFieldsState,
|
savedViewFieldsState,
|
||||||
savedViewFiltersByKeySelector: savedViewFiltersByKeySelector,
|
savedViewFiltersByKeySelector,
|
||||||
savedViewFiltersState,
|
savedViewFiltersState,
|
||||||
savedViewSortsByKeySelector: savedViewSortsByKeySelector,
|
savedViewSortsByKeySelector,
|
||||||
savedViewSortsState,
|
savedViewSortsState,
|
||||||
viewEditModeState,
|
viewEditModeState,
|
||||||
viewObjectMetadataIdState,
|
viewObjectMetadataIdState,
|
||||||
|
@ -87,7 +87,7 @@ export const SettingsObjectNewFieldStep1 = () => {
|
|||||||
metadataField.isActive ===
|
metadataField.isActive ===
|
||||||
activeObjectMetadataItem.fields[index].isActive
|
activeObjectMetadataItem.fields[index].isActive
|
||||||
) {
|
) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadataField.isActive
|
return metadataField.isActive
|
||||||
|
14
packages/twenty-front/tsconfig.app.json
Normal file
14
packages/twenty-front/tsconfig.app.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc"
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"jest.config.ts"
|
||||||
|
],
|
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
@ -29,6 +29,17 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"files": [],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
16
packages/twenty-front/tsconfig.spec.json
Normal file
16
packages/twenty-front/tsconfig.spec.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"jest.config.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
|
".storybook/**/*",
|
||||||
|
"**/*.stories.tsx"
|
||||||
|
]
|
||||||
|
}
|
@ -111,13 +111,12 @@
|
|||||||
"@nestjs/cli": "^9.0.0",
|
"@nestjs/cli": "^9.0.0",
|
||||||
"@nestjs/schematics": "^9.0.0",
|
"@nestjs/schematics": "^9.0.0",
|
||||||
"@nestjs/testing": "^9.0.0",
|
"@nestjs/testing": "^9.0.0",
|
||||||
"@stylistic/eslint-plugin": "^1.5.0",
|
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bytes": "^3.1.1",
|
"@types/bytes": "^3.1.1",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/graphql-fields": "^1.3.6",
|
"@types/graphql-fields": "^1.3.6",
|
||||||
"@types/graphql-upload": "^8.0.12",
|
"@types/graphql-upload": "^8.0.12",
|
||||||
"@types/jest": "28.1.8",
|
"@types/jest": "^29.5.11",
|
||||||
"@types/lodash.isempty": "^4.4.7",
|
"@types/lodash.isempty": "^4.4.7",
|
||||||
"@types/lodash.isequal": "^4.5.7",
|
"@types/lodash.isequal": "^4.5.7",
|
||||||
"@types/lodash.isobject": "^3.0.7",
|
"@types/lodash.isobject": "^3.0.7",
|
||||||
@ -125,27 +124,15 @@
|
|||||||
"@types/lodash.snakecase": "^4.1.7",
|
"@types/lodash.snakecase": "^4.1.7",
|
||||||
"@types/lodash.upperfirst": "^4.3.7",
|
"@types/lodash.upperfirst": "^4.3.7",
|
||||||
"@types/ms": "^0.7.31",
|
"@types/ms": "^0.7.31",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^20.10.6",
|
||||||
"@types/passport-google-oauth20": "^2.0.11",
|
"@types/passport-google-oauth20": "^2.0.11",
|
||||||
"@types/passport-jwt": "^3.0.8",
|
"@types/passport-jwt": "^3.0.8",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
|
||||||
"eslint": "^8.0.1",
|
|
||||||
"eslint-config-prettier": "^8.3.0",
|
|
||||||
"eslint-plugin-import": "^2.27.5",
|
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
|
||||||
"eslint-plugin-unused-imports": "^3.0.0",
|
|
||||||
"jest": "28.1.3",
|
|
||||||
"prettier": "^2.3.2",
|
|
||||||
"source-map-support": "^0.5.20",
|
"source-map-support": "^0.5.20",
|
||||||
"supertest": "^6.1.3",
|
"supertest": "^6.1.3",
|
||||||
"ts-jest": "28.0.8",
|
|
||||||
"ts-loader": "^9.2.3",
|
"ts-loader": "^9.2.3",
|
||||||
"ts-node": "^10.0.0",
|
"tsconfig-paths": "4.1.0"
|
||||||
"tsconfig-paths": "4.1.0",
|
|
||||||
"typescript": "^4.9.4"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"graphql": "16.8.0"
|
"graphql": "16.8.0"
|
||||||
|
@ -30,9 +30,8 @@ export class GoogleGmailService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
const connectedAccount = await workspaceDataSource?.query(
|
const connectedAccount = await workspaceDataSource?.query(
|
||||||
`SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "handle" = $1 AND "provider" = $2 AND "accountOwnerId" = $3`,
|
`SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "handle" = $1 AND "provider" = $2 AND "accountOwnerId" = $3`,
|
||||||
|
@ -51,9 +51,8 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
|||||||
workspace.id,
|
workspace.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
const apiKey = await workspaceDataSource?.query(
|
const apiKey = await workspaceDataSource?.query(
|
||||||
`SELECT * FROM ${dataSourceMetadata.schema}."apiKey" WHERE id = '${payload.jti}'`,
|
`SELECT * FROM ${dataSourceMetadata.schema}."apiKey" WHERE id = '${payload.jti}'`,
|
||||||
|
@ -16,9 +16,8 @@ export class TimelineMessagingService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
// 10 first threads This hard limit is just for the POC, we will implement pagination later
|
// 10 first threads This hard limit is just for the POC, we will implement pagination later
|
||||||
const messageThreads = await workspaceDataSource?.query(
|
const messageThreads = await workspaceDataSource?.query(
|
||||||
@ -80,9 +79,8 @@ export class TimelineMessagingService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
const personIds = await workspaceDataSource?.query(
|
const personIds = await workspaceDataSource?.query(
|
||||||
`
|
`
|
||||||
|
@ -106,11 +106,14 @@ const computeSchemaComponent = (
|
|||||||
|
|
||||||
if (requiredFields?.length) {
|
if (requiredFields?.length) {
|
||||||
result.required = requiredFields;
|
result.required = requiredFields;
|
||||||
result.example = requiredFields.reduce((example, requiredField) => {
|
result.example = requiredFields.reduce(
|
||||||
example[requiredField] = '';
|
(example, requiredField) => {
|
||||||
|
example[requiredField] = '';
|
||||||
|
|
||||||
return example;
|
return example;
|
||||||
}, {} as Record<string, string>);
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -119,11 +122,14 @@ const computeSchemaComponent = (
|
|||||||
export const computeSchemaComponents = (
|
export const computeSchemaComponents = (
|
||||||
objectMetadataItems: ObjectMetadataEntity[],
|
objectMetadataItems: ObjectMetadataEntity[],
|
||||||
): Record<string, OpenAPIV3.SchemaObject> => {
|
): Record<string, OpenAPIV3.SchemaObject> => {
|
||||||
return objectMetadataItems.reduce((schemas, item) => {
|
return objectMetadataItems.reduce(
|
||||||
schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item);
|
(schemas, item) => {
|
||||||
|
schemas[capitalize(item.nameSingular)] = computeSchemaComponent(item);
|
||||||
|
|
||||||
return schemas;
|
return schemas;
|
||||||
}, {} as Record<string, OpenAPIV3.SchemaObject>);
|
},
|
||||||
|
{} as Record<string, OpenAPIV3.SchemaObject>,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const computeParameterComponents = (): Record<
|
export const computeParameterComponents = (): Record<
|
||||||
|
@ -25,9 +25,8 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
user.defaultWorkspace.id,
|
user.defaultWorkspace.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceMembers = await workspaceDataSource?.query(
|
const workspaceMembers = await workspaceDataSource?.query(
|
||||||
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${user.id}'`,
|
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${user.id}'`,
|
||||||
@ -55,9 +54,8 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
user.defaultWorkspace.id,
|
user.defaultWorkspace.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
await workspaceDataSource?.query(
|
await workspaceDataSource?.query(
|
||||||
`INSERT INTO ${dataSourceMetadata.schema}."workspaceMember"
|
`INSERT INTO ${dataSourceMetadata.schema}."workspaceMember"
|
||||||
|
@ -76,9 +76,8 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
|||||||
this.workspaceId,
|
this.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
if (!workspaceDataSource) {
|
||||||
throw new Error('Could not connect to workspace data source');
|
throw new Error('Could not connect to workspace data source');
|
||||||
|
@ -49,9 +49,8 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
|||||||
this.isDatasourceInitializing.set(dataSource.id, true);
|
this.isDatasourceInitializing.set(dataSource.id, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dataSourceInstance = await this.createAndInitializeDataSource(
|
const dataSourceInstance =
|
||||||
dataSource,
|
await this.createAndInitializeDataSource(dataSource);
|
||||||
);
|
|
||||||
|
|
||||||
this.dataSources.set(dataSource.id, dataSourceInstance);
|
this.dataSources.set(dataSource.id, dataSourceInstance);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ModuleRef } from '@nestjs/core';
|
import { ModuleRef } from '@nestjs/core';
|
||||||
|
|
||||||
import { QueueJobOptions } from 'src/integrations/message-queue/drivers/interfaces/job-options.interface';
|
|
||||||
import { MessageQueueDriver } from 'src/integrations/message-queue/drivers/interfaces/message-queue-driver.interface';
|
import { MessageQueueDriver } from 'src/integrations/message-queue/drivers/interfaces/message-queue-driver.interface';
|
||||||
import {
|
import {
|
||||||
MessageQueueJob,
|
MessageQueueJob,
|
||||||
@ -17,7 +16,6 @@ export class SyncDriver implements MessageQueueDriver {
|
|||||||
_queueName: MessageQueue,
|
_queueName: MessageQueue,
|
||||||
jobName: string,
|
jobName: string,
|
||||||
data: T,
|
data: T,
|
||||||
_options?: QueueJobOptions | undefined,
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const jobClassName = getJobClassName(jobName);
|
const jobClassName = getJobClassName(jobName);
|
||||||
const job: MessageQueueJob<MessageQueueJobData> = this.jobsModuleRef.get(
|
const job: MessageQueueJob<MessageQueueJobData> = this.jobsModuleRef.get(
|
||||||
@ -28,10 +26,7 @@ export class SyncDriver implements MessageQueueDriver {
|
|||||||
return await job.handle(data);
|
return await job.handle(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
work<T>(
|
work() {
|
||||||
queueName: MessageQueue,
|
|
||||||
handler: ({ data, id }: { data: T; id: string }) => void | Promise<void>,
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,9 +112,8 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
fieldMetadataInput.workspaceId,
|
fieldMetadataInput.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: use typeorm repository
|
// TODO: use typeorm repository
|
||||||
const view = await workspaceDataSource?.query(
|
const view = await workspaceDataSource?.query(
|
||||||
@ -141,8 +140,8 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
|
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
|
||||||
("fieldMetadataId", "position", "isVisible", "size", "viewId")
|
("fieldMetadataId", "position", "isVisible", "size", "viewId")
|
||||||
VALUES ('${createdFieldMetadata.id}', '${lastPosition + 1}', true, 180, '${
|
VALUES ('${createdFieldMetadata.id}', '${lastPosition + 1}', true, 180, '${
|
||||||
view[0].id
|
view[0].id
|
||||||
}')`,
|
}')`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return createdFieldMetadata;
|
return createdFieldMetadata;
|
||||||
|
@ -56,8 +56,8 @@ type DefaultValueByFieldMetadata<T extends FieldMetadataType | 'default'> = [
|
|||||||
] extends [keyof FieldMetadataDefaultValueMapping]
|
] extends [keyof FieldMetadataDefaultValueMapping]
|
||||||
? FieldMetadataDefaultValueMapping[T] | null
|
? FieldMetadataDefaultValueMapping[T] | null
|
||||||
: T extends 'default'
|
: T extends 'default'
|
||||||
? AllFieldMetadataDefaultValueTypes | null
|
? AllFieldMetadataDefaultValueTypes | null
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export type FieldMetadataDefaultValue<
|
export type FieldMetadataDefaultValue<
|
||||||
T extends FieldMetadataType | 'default' = 'default',
|
T extends FieldMetadataType | 'default' = 'default',
|
||||||
@ -68,10 +68,10 @@ type FieldMetadataDefaultValueExtractNestedType<T> = T extends {
|
|||||||
}
|
}
|
||||||
? U
|
? U
|
||||||
: T extends object
|
: T extends object
|
||||||
? { [K in keyof T]: T[K] } extends { value: infer V }
|
? { [K in keyof T]: T[K] } extends { value: infer V }
|
||||||
? V
|
? V
|
||||||
: T[keyof T]
|
: T[keyof T]
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
type FieldMetadataDefaultValueExtractedTypes = {
|
type FieldMetadataDefaultValueExtractedTypes = {
|
||||||
[K in keyof FieldMetadataDefaultValueMapping]: FieldMetadataDefaultValueExtractNestedType<
|
[K in keyof FieldMetadataDefaultValueMapping]: FieldMetadataDefaultValueExtractNestedType<
|
||||||
|
@ -14,8 +14,8 @@ type OptionsByFieldMetadata<T extends FieldMetadataType | 'default'> =
|
|||||||
T extends keyof FieldMetadataOptionsMapping
|
T extends keyof FieldMetadataOptionsMapping
|
||||||
? FieldMetadataOptionsMapping[T]
|
? FieldMetadataOptionsMapping[T]
|
||||||
: T extends 'default'
|
: T extends 'default'
|
||||||
? FieldMetadataDefaultOptions[] | FieldMetadataComplexOptions[]
|
? FieldMetadataDefaultOptions[] | FieldMetadataComplexOptions[]
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export type FieldMetadataOptions<
|
export type FieldMetadataOptions<
|
||||||
T extends FieldMetadataType | 'default' = 'default',
|
T extends FieldMetadataType | 'default' = 'default',
|
||||||
|
@ -34,8 +34,8 @@ type TypeByFieldMetadata<T extends FieldMetadataType | 'default'> = [
|
|||||||
] extends [keyof FieldMetadataTypeMapping]
|
] extends [keyof FieldMetadataTypeMapping]
|
||||||
? FieldMetadataTypeMapping[T]
|
? FieldMetadataTypeMapping[T]
|
||||||
: T extends 'default'
|
: T extends 'default'
|
||||||
? AllFieldMetadataTypes
|
? AllFieldMetadataTypes
|
||||||
: FieldMetadataTargetColumnMapValue;
|
: FieldMetadataTargetColumnMapValue;
|
||||||
|
|
||||||
export type FieldMetadataTargetColumnMap<
|
export type FieldMetadataTargetColumnMap<
|
||||||
T extends FieldMetadataType | 'default' = 'default',
|
T extends FieldMetadataType | 'default' = 'default',
|
||||||
|
@ -281,9 +281,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
createdObjectMetadata.workspaceId,
|
createdObjectMetadata.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
const view = await workspaceDataSource?.query(
|
const view = await workspaceDataSource?.query(
|
||||||
`INSERT INTO ${dataSourceMetadata.schema}."view"
|
`INSERT INTO ${dataSourceMetadata.schema}."view"
|
||||||
@ -300,8 +299,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
|
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
|
||||||
("fieldMetadataId", "position", "isVisible", "size", "viewId")
|
("fieldMetadataId", "position", "isVisible", "size", "viewId")
|
||||||
VALUES ('${field.id}', '${index - 1}', true, 180, '${
|
VALUES ('${field.id}', '${index - 1}', true, 180, '${
|
||||||
view[0].id
|
view[0].id
|
||||||
}') RETURNING *`,
|
}') RETURNING *`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,11 +100,14 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadataMap = objectMetadataEntries.reduce((acc, curr) => {
|
const objectMetadataMap = objectMetadataEntries.reduce(
|
||||||
acc[curr.id] = curr;
|
(acc, curr) => {
|
||||||
|
acc[curr.id] = curr;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as { [key: string]: ObjectMetadataEntity });
|
},
|
||||||
|
{} as { [key: string]: ObjectMetadataEntity },
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
objectMetadataMap[relationMetadataInput.fromObjectMetadataId] ===
|
objectMetadataMap[relationMetadataInput.fromObjectMetadataId] ===
|
||||||
|
@ -25,7 +25,7 @@ export function mergeDefaultOptions<
|
|||||||
return query.getRawMany();
|
return query.getRawMany();
|
||||||
},
|
},
|
||||||
getCursor: (record: Record | undefined) =>
|
getCursor: (record: Record | undefined) =>
|
||||||
({ id: (record as unknown as { id: string })?.id } as unknown as Cursor),
|
({ id: (record as unknown as { id: string })?.id }) as unknown as Cursor,
|
||||||
encodeCursor: (cursor: Cursor) =>
|
encodeCursor: (cursor: Cursor) =>
|
||||||
Buffer.from((cursor as unknown as { id: string }).id.toString()).toString(
|
Buffer.from((cursor as unknown as { id: string }).id.toString()).toString(
|
||||||
'base64',
|
'base64',
|
||||||
@ -33,9 +33,9 @@ export function mergeDefaultOptions<
|
|||||||
decodeCursor: (cursorString: string) =>
|
decodeCursor: (cursorString: string) =>
|
||||||
({
|
({
|
||||||
id: Buffer.from(cursorString, 'base64').toString(),
|
id: Buffer.from(cursorString, 'base64').toString(),
|
||||||
} as unknown as Cursor),
|
}) as unknown as Cursor,
|
||||||
recordToEdge: (record: Record) =>
|
recordToEdge: (record: Record) =>
|
||||||
({ node: record } as unknown as Omit<CustomEdge, 'cursor'>),
|
({ node: record }) as unknown as Omit<CustomEdge, 'cursor'>,
|
||||||
resolveInfo: null,
|
resolveInfo: null,
|
||||||
...pOptions,
|
...pOptions,
|
||||||
};
|
};
|
||||||
|
@ -29,9 +29,8 @@ export class FetchBatchMessagesService {
|
|||||||
'batch_gmail_messages',
|
'batch_gmail_messages',
|
||||||
);
|
);
|
||||||
|
|
||||||
const messages = await this.formatBatchResponsesAsGmailMessages(
|
const messages =
|
||||||
batchResponses,
|
await this.formatBatchResponsesAsGmailMessages(batchResponses);
|
||||||
);
|
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
@ -46,9 +45,8 @@ export class FetchBatchMessagesService {
|
|||||||
'batch_gmail_threads',
|
'batch_gmail_threads',
|
||||||
);
|
);
|
||||||
|
|
||||||
const threads = await this.formatBatchResponsesAsGmailThreads(
|
const threads =
|
||||||
batchResponses,
|
await this.formatBatchResponsesAsGmailThreads(batchResponses);
|
||||||
);
|
|
||||||
|
|
||||||
return threads;
|
return threads;
|
||||||
}
|
}
|
||||||
@ -242,9 +240,8 @@ export class FetchBatchMessagesService {
|
|||||||
): Promise<GmailMessage[]> {
|
): Promise<GmailMessage[]> {
|
||||||
const formattedResponses = await Promise.all(
|
const formattedResponses = await Promise.all(
|
||||||
batchResponses.map(async (response) => {
|
batchResponses.map(async (response) => {
|
||||||
const formattedResponse = await this.formatBatchResponseAsGmailMessage(
|
const formattedResponse =
|
||||||
response,
|
await this.formatBatchResponseAsGmailMessage(response);
|
||||||
);
|
|
||||||
|
|
||||||
return formattedResponse;
|
return formattedResponse;
|
||||||
}),
|
}),
|
||||||
@ -292,9 +289,8 @@ export class FetchBatchMessagesService {
|
|||||||
): Promise<GmailThread[]> {
|
): Promise<GmailThread[]> {
|
||||||
const formattedResponses = await Promise.all(
|
const formattedResponses = await Promise.all(
|
||||||
batchResponses.map(async (response) => {
|
batchResponses.map(async (response) => {
|
||||||
const formattedResponse = await this.formatBatchResponseAsGmailThread(
|
const formattedResponse =
|
||||||
response,
|
await this.formatBatchResponseAsGmailThread(response);
|
||||||
);
|
|
||||||
|
|
||||||
return formattedResponse;
|
return formattedResponse;
|
||||||
}),
|
}),
|
||||||
|
@ -44,9 +44,8 @@ export class FetchWorkspaceMessagesService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
if (!workspaceDataSource) {
|
||||||
throw new Error('No workspace data source found');
|
throw new Error('No workspace data source found');
|
||||||
|
@ -23,9 +23,8 @@ export class RefreshAccessTokenService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceDataSource = await this.typeORMService.connectToDataSource(
|
const workspaceDataSource =
|
||||||
dataSourceMetadata,
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
if (!workspaceDataSource) {
|
if (!workspaceDataSource) {
|
||||||
throw new Error('No workspace data source found');
|
throw new Error('No workspace data source found');
|
||||||
|
@ -27,9 +27,8 @@ export class WorkspaceDataSourceService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dataSource = await this.typeormService.connectToDataSource(
|
const dataSource =
|
||||||
dataSourceMetadata,
|
await this.typeormService.connectToDataSource(dataSourceMetadata);
|
||||||
);
|
|
||||||
|
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -146,8 +146,8 @@ export class WorkspaceMigrationEnumService {
|
|||||||
UPDATE "${schemaName}"."${tableName}"
|
UPDATE "${schemaName}"."${tableName}"
|
||||||
SET "${columnDefinition.columnName}" = ${defaultValue}
|
SET "${columnDefinition.columnName}" = ${defaultValue}
|
||||||
WHERE "${columnDefinition.columnName}" NOT IN (${enumValues
|
WHERE "${columnDefinition.columnName}" NOT IN (${enumValues
|
||||||
.map((e) => `'${e}'`)
|
.map((e) => `'${e}'`)
|
||||||
.join(', ')})
|
.join(', ')})
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +38,11 @@ export class CreateManyQueryFactory {
|
|||||||
insertInto${
|
insertInto${
|
||||||
options.targetTableName
|
options.targetTableName
|
||||||
}Collection(objects: ${stringifyWithoutKeyQuote(
|
}Collection(objects: ${stringifyWithoutKeyQuote(
|
||||||
computedArgs.data.map((datum) => ({
|
computedArgs.data.map((datum) => ({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
...datum,
|
...datum,
|
||||||
})),
|
})),
|
||||||
)}) {
|
)}) {
|
||||||
affectedCount
|
affectedCount
|
||||||
records {
|
records {
|
||||||
${fieldsString}
|
${fieldsString}
|
||||||
|
@ -25,8 +25,8 @@ export class DeleteManyQueryFactory {
|
|||||||
deleteFrom${
|
deleteFrom${
|
||||||
options.targetTableName
|
options.targetTableName
|
||||||
}Collection(filter: ${stringifyWithoutKeyQuote(
|
}Collection(filter: ${stringifyWithoutKeyQuote(
|
||||||
args.filter,
|
args.filter,
|
||||||
)}, atMost: 30) {
|
)}, atMost: 30) {
|
||||||
affectedCount
|
affectedCount
|
||||||
records {
|
records {
|
||||||
${fieldsString}
|
${fieldsString}
|
||||||
|
@ -38,8 +38,8 @@ export class FindManyQueryFactory {
|
|||||||
return `
|
return `
|
||||||
query {
|
query {
|
||||||
${options.targetTableName}Collection${
|
${options.targetTableName}Collection${
|
||||||
argsString ? `(${argsString})` : ''
|
argsString ? `(${argsString})` : ''
|
||||||
} {
|
} {
|
||||||
${fieldsString}
|
${fieldsString}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ export class FindOneQueryFactory {
|
|||||||
return `
|
return `
|
||||||
query {
|
query {
|
||||||
${options.targetTableName}Collection${
|
${options.targetTableName}Collection${
|
||||||
argsString ? `(${argsString})` : ''
|
argsString ? `(${argsString})` : ''
|
||||||
} {
|
} {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
${fieldsString}
|
${fieldsString}
|
||||||
|
@ -105,8 +105,8 @@ export class RelationFieldAliasFactory {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
${fieldKey}: ${referencedObjectMetadata.targetTableName}Collection${
|
${fieldKey}: ${referencedObjectMetadata.targetTableName}Collection${
|
||||||
argsString ? `(${argsString})` : ''
|
argsString ? `(${argsString})` : ''
|
||||||
} {
|
} {
|
||||||
${fieldsString}
|
${fieldsString}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -41,8 +41,8 @@ export class UpdateOneQueryFactory {
|
|||||||
update${
|
update${
|
||||||
options.targetTableName
|
options.targetTableName
|
||||||
}Collection(set: ${stringifyWithoutKeyQuote(
|
}Collection(set: ${stringifyWithoutKeyQuote(
|
||||||
argsData,
|
argsData,
|
||||||
)}, filter: { id: { eq: "${computedArgs.id}" } }) {
|
)}, filter: { id: { eq: "${computedArgs.id}" } }) {
|
||||||
affectedCount
|
affectedCount
|
||||||
records {
|
records {
|
||||||
${fieldsString}
|
${fieldsString}
|
||||||
|
@ -72,14 +72,17 @@ export class EnumTypeDefinitionFactory {
|
|||||||
return new GraphQLEnumType({
|
return new GraphQLEnumType({
|
||||||
name: `${pascalCase(objectName)}${pascalCase(fieldMetadata.name)}Enum`,
|
name: `${pascalCase(objectName)}${pascalCase(fieldMetadata.name)}Enum`,
|
||||||
description: fieldMetadata.description,
|
description: fieldMetadata.description,
|
||||||
values: enumOptions.reduce((acc, enumOption) => {
|
values: enumOptions.reduce(
|
||||||
acc[enumOption.value] = {
|
(acc, enumOption) => {
|
||||||
value: enumOption.value,
|
acc[enumOption.value] = {
|
||||||
description: enumOption.label,
|
value: enumOption.value,
|
||||||
};
|
description: enumOption.label,
|
||||||
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as { [key: string]: { value: string; description: string } }),
|
},
|
||||||
|
{} as { [key: string]: { value: string; description: string } },
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,8 @@ export class WorkspaceSchemaStorageService {
|
|||||||
(await this.cacheVersionMemoryStorageService.read({
|
(await this.cacheVersionMemoryStorageService.read({
|
||||||
key: workspaceId,
|
key: workspaceId,
|
||||||
})) ?? '0';
|
})) ?? '0';
|
||||||
const latestVersion = await this.workspaceCacheVersionService.getVersion(
|
const latestVersion =
|
||||||
workspaceId,
|
await this.workspaceCacheVersionService.getVersion(workspaceId);
|
||||||
);
|
|
||||||
|
|
||||||
if (currentVersion !== latestVersion) {
|
if (currentVersion !== latestVersion) {
|
||||||
// Invalidate cache if version mismatch is detected
|
// Invalidate cache if version mismatch is detected
|
||||||
|
@ -36,18 +36,24 @@ export const mapObjectMetadataByUniqueIdentifier = <
|
|||||||
>(
|
>(
|
||||||
arr: T[],
|
arr: T[],
|
||||||
): Record<string, Omit<T, 'fields'> & { fields: Record<string, U> }> => {
|
): Record<string, Omit<T, 'fields'> & { fields: Record<string, U> }> => {
|
||||||
return arr.reduce((acc, curr) => {
|
return arr.reduce(
|
||||||
acc[curr.nameSingular] = {
|
(acc, curr) => {
|
||||||
...curr,
|
acc[curr.nameSingular] = {
|
||||||
fields: curr.fields.reduce((acc, curr) => {
|
...curr,
|
||||||
acc[curr.name] = curr;
|
fields: curr.fields.reduce(
|
||||||
|
(acc, curr) => {
|
||||||
|
acc[curr.name] = curr;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, U>),
|
},
|
||||||
};
|
{} as Record<string, U>,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, Omit<T, 'fields'> & { fields: Record<string, U> }>);
|
},
|
||||||
|
{} as Record<string, Omit<T, 'fields'> & { fields: Record<string, U> }>,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const convertStringifiedFieldsToJSON = <
|
export const convertStringifiedFieldsToJSON = <
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user