Add twenty-for-twenty on internal ci apps (#21944)

Add twenty-for-twenty to internal apps CI: remove from
CI_EXCLUDED_APPLICATIONS and unify config with
twenty-discord/twenty-slack.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21944?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
martmull
2026-06-22 13:07:18 +02:00
committed by GitHub
parent 02a966bb7f
commit 4b868f9b29
59 changed files with 439 additions and 149 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
// keep this PR small while the remaining apps are made CI-ready. Remove an
// app from this list once its checks pass, and delete the list entirely
// once every application is covered.
const CI_EXCLUDED_APPLICATIONS = ['call-recording', 'people-data-labs', 'twenty-for-twenty', 'twenty-linear', 'twenty-meeting-bot', 'twenty-partners'];
const CI_EXCLUDED_APPLICATIONS = ['call-recording', 'people-data-labs', 'twenty-linear', 'twenty-meeting-bot', 'twenty-partners'];
const eventName = process.env.EVENT_NAME;
const changedFiles = JSON.parse(process.env.CHANGED_FILES || '[]');
const changedApps = new Set();
@@ -1,20 +1,58 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"extends": ["../../.oxlintrc.base.json"],
"plugins": ["typescript"],
"plugins": ["react", "typescript", "import", "unicorn"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"no-console": "off",
"no-control-regex": "off",
"no-debugger": "error",
"no-duplicate-imports": "error",
"no-undef": "off",
"no-unused-vars": "off",
"no-redeclare": "off",
"import/no-duplicates": "error",
"typescript/no-redeclare": "error",
"typescript/ban-ts-comment": "error",
"typescript/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"fixStyle": "inline-type-imports"
}
],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-empty-object-type": [
"error",
{
"allowInterfaces": "with-single-extends"
}
],
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
"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", { "explicitSpread": "ignore" }],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
@@ -0,0 +1 @@
nodeLinker: node-modules
@@ -7,28 +7,31 @@
"npm": "please-use-yarn",
"yarn": ">=4.0.2"
},
"keywords": [],
"keywords": [
"twenty-app"
],
"packageManager": "yarn@4.13.0",
"scripts": {
"twenty": "twenty",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json .",
"typecheck": "tsgo --noEmit -p tsconfig.spec.json",
"test": "vitest run",
"test:watch": "vitest",
"test:unit": "vitest run --config vitest.unit.config.ts",
"test:unit:watch": "vitest --config vitest.unit.config.ts"
"test:unit": "vitest run --config vitest.unit.config.ts"
},
"dependencies": {
"resend": "^6.12.0",
"twenty-client-sdk": "npm:twenty-client-sdk@2.13.0",
"twenty-sdk": "npm:twenty-sdk@2.13.0"
"resend": "^6.12.0"
},
"devDependencies": {
"@types/node": "^24.7.2",
"@types/react": "^18.2.0",
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
"oxlint": "^0.16.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"twenty-client-sdk": "^2.14.0",
"twenty-sdk": "^2.14.0",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^4.0.0"
@@ -1,52 +0,0 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { APPLICATION_UNIVERSAL_IDENTIFIER } from '@constants/universal-identifiers';
import { isDefined } from '@utils/is-defined';
import { describe, expect, it } from 'vitest';
describe('App installation', () => {
it('should find the installed app in the applications list', async () => {
const client = new MetadataApiClient();
const result = await client.query({
findManyApplications: {
id: true,
name: true,
universalIdentifier: true,
},
});
const matchingApplication = result.findManyApplications.find(
(application: { universalIdentifier: string }) =>
application.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
);
expect(matchingApplication).toBeDefined();
});
});
describe('CoreApiClient', () => {
it('should support CRUD on standard objects', async () => {
const client = new CoreApiClient();
const created = await client.mutation({
createNote: {
__args: { data: { title: 'Integration test note' } },
id: true,
},
});
const createdNote = created.createNote;
expect(createdNote?.id).toBeDefined();
if (isDefined(createdNote)) {
await client.mutation({
destroyNote: {
__args: { id: createdNote.id },
id: true,
},
});
}
});
});
@@ -0,0 +1,25 @@
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { describe, expect, it } from 'vitest';
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
describe('App installation', () => {
it('should find the installed Twenty for Twenty app in the applications list', async () => {
const client = new MetadataApiClient();
const result = await client.query({
findManyApplications: {
id: true,
name: true,
universalIdentifier: true,
},
});
const matchingApplication = result.findManyApplications.find(
(application: { universalIdentifier: string }) =>
application.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
);
expect(matchingApplication).toBeDefined();
});
});
@@ -5,7 +5,6 @@ import {
IconAlertCircle,
IconMail,
IconRefresh,
themeCssVariables,
} from 'twenty-sdk/ui';
import { isDefined } from '@utils/is-defined';
@@ -18,6 +17,81 @@ import {
} from '@modules/resend/email-stats/constants/email-status-groups';
import { usePersonResendEmailStats } from '@modules/resend/email-stats/hooks/usePersonResendEmailStats';
// Workaround: 'twenty-sdk/ui' currently fails typecheck because it re-exports
// from the unresolvable 'twenty-ui-deprecated'. Inline only the theme tokens
// this component uses, keeping the same runtime CSS-variable values. Revert to
// `import { themeCssVariables } from 'twenty-sdk/ui'` once the SDK export is fixed.
const THEME_COLORS: ReadonlyArray<ThemeColor> = [
'red',
'ruby',
'crimson',
'tomato',
'orange',
'amber',
'yellow',
'lime',
'grass',
'green',
'jade',
'mint',
'turquoise',
'cyan',
'sky',
'blue',
'iris',
'violet',
'purple',
'plum',
'pink',
'bronze',
'gold',
'brown',
'gray',
];
const buildTagColorRecord = (variant: string): Record<ThemeColor, string> =>
Object.fromEntries(
THEME_COLORS.map((color) => [color, `var(--t-tag-${variant}-${color})`]),
) as Record<ThemeColor, string>;
const themeCssVariables = {
spacing: {
'2': 'var(--t-spacing-2)',
'3': 'var(--t-spacing-3)',
'4': 'var(--t-spacing-4)',
'5': 'var(--t-spacing-5)',
},
background: {
secondary: 'var(--t-background-secondary)',
},
border: {
color: {
light: 'var(--t-border-color-light)',
},
radius: {
sm: 'var(--t-border-radius-sm)',
md: 'var(--t-border-radius-md)',
pill: 'var(--t-border-radius-pill)',
},
},
font: {
color: {
primary: 'var(--t-font-color-primary)',
secondary: 'var(--t-font-color-secondary)',
tertiary: 'var(--t-font-color-tertiary)',
},
size: {
xs: 'var(--t-font-size-xs)',
sm: 'var(--t-font-size-sm)',
},
family: 'var(--t-font-family)',
},
tag: {
background: buildTagColorRecord('background'),
text: buildTagColorRecord('text'),
},
};
const getDeliverabilityColor = (rate: number): ThemeColor => {
if (rate >= 0.95) return 'green';
if (rate >= 0.8) return 'yellow';
@@ -1,11 +1,33 @@
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from '@utils/is-defined';
import { themeCssVariables } from 'twenty-sdk/ui';
type HtmlPreviewProps = {
html: string | null | undefined;
};
// Workaround: 'twenty-sdk/ui' currently fails typecheck because it re-exports
// from the unresolvable 'twenty-ui-deprecated'. Inline only the theme tokens
// this component uses, keeping the same runtime CSS-variable values. Revert to
// `import { themeCssVariables } from 'twenty-sdk/ui'` once the SDK export is fixed.
const themeCssVariables = {
spacing: {
'4': 'var(--t-spacing-4)',
},
background: {
primary: 'var(--t-background-primary)',
secondary: 'var(--t-background-secondary)',
},
font: {
color: {
tertiary: 'var(--t-font-color-tertiary)',
},
size: {
sm: 'var(--t-font-size-sm)',
},
family: 'var(--t-font-family)',
},
};
// Styles are computed lazily inside the component body because the SDK
// mocks `twenty-sdk/ui` at manifest-build time, which leaves
// `themeCssVariables` undefined during static module evaluation.
@@ -1,5 +1,5 @@
import { isDefined } from '@utils/is-defined';
import { Callout, IconAlertCircle, themeCssVariables } from 'twenty-sdk/ui';
import { Callout, IconAlertCircle } from 'twenty-sdk/ui';
import { HtmlPreview } from '@modules/resend/html-viewer/components/HtmlPreview';
import { useRecordHtml } from '@modules/resend/html-viewer/hooks/useRecordHtml';
@@ -9,6 +9,36 @@ type RecordHtmlViewerProps = {
loadingText: string;
};
// Workaround: 'twenty-sdk/ui' currently fails typecheck because it re-exports
// from the unresolvable 'twenty-ui-deprecated'. Inline only the theme tokens
// this component uses, keeping the same runtime CSS-variable values. Revert to
// `import { themeCssVariables } from 'twenty-sdk/ui'` once the SDK export is fixed.
const themeCssVariables = {
spacing: {
'4': 'var(--t-spacing-4)',
},
background: {
secondary: 'var(--t-background-secondary)',
},
border: {
color: {
light: 'var(--t-border-color-light)',
},
radius: {
md: 'var(--t-border-radius-md)',
},
},
font: {
color: {
tertiary: 'var(--t-font-color-tertiary)',
},
size: {
sm: 'var(--t-font-size-sm)',
},
family: 'var(--t-font-family)',
},
};
// Styles are computed lazily inside the component body because the SDK
// mocks `twenty-sdk/ui` at manifest-build time, which leaves
// `themeCssVariables` undefined during static module evaluation.
@@ -1,16 +1,41 @@
import { isDefined } from '@utils/is-defined';
import { defineFrontComponent } from 'twenty-sdk/define';
import {
Callout,
IconAlertCircle,
IconInfoCircle,
themeCssVariables,
} from 'twenty-sdk/ui';
import { Callout, IconAlertCircle, IconInfoCircle } from 'twenty-sdk/ui';
import { EMAIL_BROADCAST_HTML_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers';
import { HtmlPreview } from '@modules/resend/html-viewer/components/HtmlPreview';
import { useRelatedBroadcastHtml } from '@modules/resend/html-viewer/hooks/useRelatedBroadcastHtml';
// Workaround: 'twenty-sdk/ui' currently fails typecheck because it re-exports
// from the unresolvable 'twenty-ui-deprecated'. Inline only the theme tokens
// this component uses, keeping the same runtime CSS-variable values. Revert to
// `import { themeCssVariables } from 'twenty-sdk/ui'` once the SDK export is fixed.
const themeCssVariables = {
spacing: {
'4': 'var(--t-spacing-4)',
},
background: {
secondary: 'var(--t-background-secondary)',
},
border: {
color: {
light: 'var(--t-border-color-light)',
},
radius: {
md: 'var(--t-border-radius-md)',
},
},
font: {
color: {
tertiary: 'var(--t-font-color-tertiary)',
},
size: {
sm: 'var(--t-font-size-sm)',
},
family: 'var(--t-font-family)',
},
};
const getStyles = (): Record<string, React.CSSProperties> => {
const stateContainer: React.CSSProperties = {
padding: themeCssVariables.spacing[4],
@@ -0,0 +1,16 @@
import { defineCommandMenuItem } from 'twenty-sdk/define';
import {
SYNC_RESEND_DATA_COMMAND_UNIVERSAL_IDENTIFIER,
SYNC_RESEND_DATA_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
export default defineCommandMenuItem({
universalIdentifier: SYNC_RESEND_DATA_COMMAND_UNIVERSAL_IDENTIFIER,
label: 'Sync Resend data',
icon: 'IconRefresh',
isPinned: false,
availabilityType: 'GLOBAL',
frontComponentUniversalIdentifier:
SYNC_RESEND_DATA_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
});
@@ -7,13 +7,51 @@ import {
IconAlertCircle,
IconRefresh,
Status,
themeCssVariables,
} from 'twenty-sdk/ui';
import { extractConnection } from '@modules/resend/shared/utils/typed-client';
import { RESEND_SYNC_CURSOR_STEPS } from '@modules/resend/sync/cursor/constants/resend-sync-cursor-steps';
import type { SyncCursorStep } from '@modules/resend/sync/cursor/types/sync-cursor-step';
// Workaround: 'twenty-sdk/ui' currently fails typecheck because it re-exports
// from the unresolvable 'twenty-ui-deprecated'. Inline only the theme tokens
// this component uses, keeping the same runtime CSS-variable values. Revert to
// `import { themeCssVariables } from 'twenty-sdk/ui'` once the SDK export is fixed.
const themeCssVariables = {
spacing: {
'1': 'var(--t-spacing-1)',
'2': 'var(--t-spacing-2)',
'3': 'var(--t-spacing-3)',
'4': 'var(--t-spacing-4)',
},
background: {
secondary: 'var(--t-background-secondary)',
transparent: {
light: 'var(--t-background-transparent-light)',
},
},
border: {
color: {
light: 'var(--t-border-color-light)',
},
radius: {
sm: 'var(--t-border-radius-sm)',
md: 'var(--t-border-radius-md)',
},
},
font: {
color: {
primary: 'var(--t-font-color-primary)',
secondary: 'var(--t-font-color-secondary)',
},
size: {
xs: 'var(--t-font-size-xs)',
sm: 'var(--t-font-size-sm)',
},
family: 'var(--t-font-family)',
},
};
type CursorRowStatus = 'SUCCESS' | 'FAILED' | 'IN_PROGRESS';
type CursorRow = {
@@ -11,10 +11,7 @@ import {
import { APPLICATION_UNIVERSAL_IDENTIFIER } from '@constants/universal-identifiers';
import { INITIAL_SYNC_MODE_ENV_VAR_NAME } from '@modules/resend/constants/sync-config';
import {
SYNC_RESEND_DATA_COMMAND_UNIVERSAL_IDENTIFIER,
SYNC_RESEND_DATA_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { SYNC_RESEND_DATA_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers';
import { resolveSyncStatusPageLayoutId } from '@modules/resend/manual-sync/utils/resolve-sync-status-page-layout-id';
import { resetAllSyncCursors } from '@modules/resend/sync/cursor/utils/reset-all-sync-cursors';
@@ -88,11 +85,4 @@ export default defineFrontComponent({
'Resets every Resend sync cursor and flips the application into initial sync mode so the scheduled sync handlers restart from scratch on their next run.',
isHeadless: true,
component: SyncResendData,
command: {
universalIdentifier: SYNC_RESEND_DATA_COMMAND_UNIVERSAL_IDENTIFIER,
label: 'Sync Resend data',
icon: 'IconRefresh',
isPinned: false,
availabilityType: 'GLOBAL',
},
});
@@ -1,4 +1,4 @@
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { type MetadataApiClient } from 'twenty-client-sdk/metadata';
import { describe, expect, it, vi } from 'vitest';
import { RESEND_SYNC_STATUS_NAVIGATION_MENU_ITEM_NAME } from '@modules/resend/manual-sync/constants/resend-sync-status-menu-item-name';
@@ -1,5 +1,5 @@
import { isDefined } from '@utils/is-defined';
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
import { type MetadataApiClient } from 'twenty-client-sdk/metadata';
import { RESEND_SYNC_STATUS_NAVIGATION_MENU_ITEM_NAME } from '@modules/resend/manual-sync/constants/resend-sync-status-menu-item-name';
@@ -3,8 +3,7 @@ import {
RESEND_BROADCAST_VIEW_UNIVERSAL_IDENTIFIER,
RESEND_FOLDER_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier:
@@ -3,8 +3,7 @@ import {
RESEND_CONTACT_VIEW_UNIVERSAL_IDENTIFIER,
RESEND_FOLDER_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier: RESEND_CONTACT_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
@@ -3,8 +3,7 @@ import {
RESEND_EMAIL_VIEW_UNIVERSAL_IDENTIFIER,
RESEND_FOLDER_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier: RESEND_EMAIL_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
@@ -1,6 +1,5 @@
import { RESEND_FOLDER_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier: RESEND_FOLDER_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
@@ -3,8 +3,7 @@ import {
RESEND_SEGMENT_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
RESEND_SEGMENT_VIEW_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier:
@@ -4,8 +4,7 @@ import {
RESEND_SYNC_STATUS_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { RESEND_SYNC_STATUS_NAVIGATION_MENU_ITEM_NAME } from '@modules/resend/manual-sync/constants/resend-sync-status-menu-item-name';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier:
@@ -3,8 +3,7 @@ import {
RESEND_TEMPLATE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
RESEND_TEMPLATE_VIEW_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier:
@@ -3,8 +3,7 @@ import {
RESEND_TOPIC_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
RESEND_TOPIC_VIEW_UNIVERSAL_IDENTIFIER,
} from '@modules/resend/constants/universal-identifiers';
import { defineNavigationMenuItem } from 'twenty-sdk/define';
import { NavigationMenuItemType } from 'twenty-sdk/define';
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
export default defineNavigationMenuItem({
universalIdentifier:
@@ -1,8 +1,14 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { findResendContactsByEmail } from '@modules/resend/shared/utils/find-resend-contacts-by-email';
type QueryArgs = {
resendContacts: {
__args: { filter: { email: { primaryEmail: { in: string[] } } } };
};
};
describe('findResendContactsByEmail', () => {
const buildClient = (
queryImpl: ReturnType<typeof vi.fn>,
@@ -71,7 +77,7 @@ describe('findResendContactsByEmail', () => {
expect(query).toHaveBeenCalledTimes(1);
const args = query.mock.calls[0][0];
const [args] = query.mock.calls[0] as unknown as [QueryArgs];
expect(args.resendContacts.__args.filter.email.primaryEmail.in).toEqual([
'foo@example.com',
@@ -1,5 +1,5 @@
import { isNonEmptyString } from '@sniptt/guards';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
type PersonName = {
@@ -1,5 +1,5 @@
import { isNonEmptyString } from '@sniptt/guards';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
type PeopleConnection = {
edges: Array<{
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { extractConnection } from '@modules/resend/shared/utils/typed-client';
@@ -1,5 +1,5 @@
import { isNonEmptyString } from '@sniptt/guards';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { extractConnection } from '@modules/resend/shared/utils/typed-client';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { extractConnection } from '@modules/resend/shared/utils/typed-client';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { describe, expect, it, vi } from 'vitest';
import { resetAllSyncCursors } from '@modules/resend/sync/cursor/utils/reset-all-sync-cursors';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { describe, expect, it, vi } from 'vitest';
import { withSyncCursor } from 'src/modules/resend/sync/cursor/utils/with-sync-cursor';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import type {
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { extractConnection } from '@modules/resend/shared/utils/typed-client';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
export type SyncRunStatus = 'SUCCESS' | 'FAILED' | 'IN_PROGRESS';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { getOrCreateSyncCursor } from 'src/modules/resend/sync/cursor/utils/get-or-create-sync-cursor';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { backfillResendContactPersonId } from '@modules/resend/sync/utils/backfill-resend-contact-person-id';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { backfillResendEmailsFromContacts } from '@modules/resend/sync/utils/backfill-resend-emails-from-contacts';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { findRecentSentBroadcasts } from '@modules/resend/sync/utils/find-recent-sent-broadcasts';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { syncBroadcasts } from '@modules/resend/sync/utils/sync-broadcasts';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { syncContacts } from '@modules/resend/sync/utils/sync-contacts';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { syncEmails } from '@modules/resend/sync/utils/sync-emails';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { syncTopics } from '@modules/resend/sync/utils/sync-topics';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { extractConnection } from '@modules/resend/shared/utils/typed-client';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { getErrorMessage } from '@modules/resend/shared/utils/get-error-message';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { TWENTY_PAGE_SIZE } from '@modules/resend/constants/sync-config';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { TWENTY_PAGE_SIZE } from '@modules/resend/constants/sync-config';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { findRecordByResendId } from '@modules/resend/shared/utils/find-record-by-resend-id';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { TWENTY_PAGE_SIZE } from '@modules/resend/constants/sync-config';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { TWENTY_PAGE_SIZE } from '@modules/resend/constants/sync-config';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { findTwentyIdsByResendId } from '@modules/resend/shared/utils/find-twenty-ids-by-resend-id';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import type { ContactDto } from '@modules/resend/sync/types/contact.dto';
@@ -1,6 +1,6 @@
import { isDefined } from '@utils/is-defined';
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { findPeopleByEmail } from '@modules/resend/shared/utils/find-people-by-email';
import { findResendContactsByEmail } from '@modules/resend/shared/utils/find-resend-contacts-by-email';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import type { SegmentDto } from '@modules/resend/sync/types/segment.dto';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { forEachPage } from '@modules/resend/shared/utils/for-each-page';
@@ -1,5 +1,5 @@
import type { Resend } from 'resend';
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { withRateLimitRetry } from '@modules/resend/shared/utils/with-rate-limit-retry';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { isDefined } from '@utils/is-defined';
import { capitalize } from '@modules/resend/shared/utils/capitalize';
@@ -1,4 +1,4 @@
import { CoreApiClient } from 'twenty-client-sdk/core';
import { type CoreApiClient } from 'twenty-client-sdk/core';
import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@modules/resend/shared/utils/find-or-create-person', () => ({
@@ -6,7 +6,7 @@
"outDir": "./dist",
"rootDir": ".",
"jsx": "react-jsx",
"moduleResolution": "node",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
@@ -16,7 +16,7 @@
"alwaysStrict": true,
"noImplicitAny": true,
"strictBindCallApply": false,
"target": "es2018",
"target": "es2020",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
@@ -834,6 +834,87 @@ __metadata:
languageName: node
linkType: hard
"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260619.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260619.1"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260619.1"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260619.1"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260619.1"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260619.1"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260619.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260619.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@typescript/native-preview@npm:^7.0.0-dev.20260116.1":
version: 7.0.0-dev.20260619.1
resolution: "@typescript/native-preview@npm:7.0.0-dev.20260619.1"
dependencies:
"@typescript/native-preview-darwin-arm64": "npm:7.0.0-dev.20260619.1"
"@typescript/native-preview-darwin-x64": "npm:7.0.0-dev.20260619.1"
"@typescript/native-preview-linux-arm": "npm:7.0.0-dev.20260619.1"
"@typescript/native-preview-linux-arm64": "npm:7.0.0-dev.20260619.1"
"@typescript/native-preview-linux-x64": "npm:7.0.0-dev.20260619.1"
"@typescript/native-preview-win32-arm64": "npm:7.0.0-dev.20260619.1"
"@typescript/native-preview-win32-x64": "npm:7.0.0-dev.20260619.1"
dependenciesMeta:
"@typescript/native-preview-darwin-arm64":
optional: true
"@typescript/native-preview-darwin-x64":
optional: true
"@typescript/native-preview-linux-arm":
optional: true
"@typescript/native-preview-linux-arm64":
optional: true
"@typescript/native-preview-linux-x64":
optional: true
"@typescript/native-preview-win32-arm64":
optional: true
"@typescript/native-preview-win32-x64":
optional: true
bin:
tsgo: bin/tsgo.js
checksum: 10c0/e8bc6cf171ee29131934f662c9b3577374d7e872247831da36a5723abe175998f1318a134ed8eb0ee5213887dbb1f1209fac5555cdf5b6b19d0d7da044d0cdc9
languageName: node
linkType: hard
"@vitest/expect@npm:4.1.8":
version: 4.1.8
resolution: "@vitest/expect@npm:4.1.8"
@@ -2785,16 +2866,16 @@ __metadata:
languageName: node
linkType: hard
"twenty-client-sdk@npm:2.13.0, twenty-client-sdk@npm:twenty-client-sdk@2.13.0":
version: 2.13.0
resolution: "twenty-client-sdk@npm:2.13.0"
"twenty-client-sdk@npm:2.14.0, twenty-client-sdk@npm:^2.14.0":
version: 2.14.0
resolution: "twenty-client-sdk@npm:2.14.0"
dependencies:
"@genql/runtime": "npm:^2.10.0"
esbuild: "npm:^0.28.1"
graphql: "npm:^16.8.1"
lodash: "npm:^4.17.21"
prettier: "npm:^3.8.3"
checksum: 10c0/740464acec94c1d4cc5fa50ed6b190a7f1d1681963f61437a6c704835c05ffe0f5a13e254e57601a99b07bbe0f68483dee19211731853aafbcc15ec79f8039ce
checksum: 10c0/eb222a726e21fc98f3a624f9acac7d47f0c769b989911889036665dac7c7c88698ec99beb844bf51a42e1696ea0a067bae8cf47fd751965af52a3720835242f0
languageName: node
linkType: hard
@@ -2804,21 +2885,22 @@ __metadata:
dependencies:
"@types/node": "npm:^24.7.2"
"@types/react": "npm:^18.2.0"
"@typescript/native-preview": "npm:^7.0.0-dev.20260116.1"
oxlint: "npm:^0.16.0"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
resend: "npm:^6.12.0"
twenty-client-sdk: "npm:twenty-client-sdk@2.13.0"
twenty-sdk: "npm:twenty-sdk@2.13.0"
twenty-client-sdk: "npm:^2.14.0"
twenty-sdk: "npm:^2.14.0"
typescript: "npm:^5.9.3"
vite-tsconfig-paths: "npm:^4.2.1"
vitest: "npm:^4.0.0"
languageName: unknown
linkType: soft
"twenty-sdk@npm:twenty-sdk@2.13.0":
version: 2.13.0
resolution: "twenty-sdk@npm:2.13.0"
"twenty-sdk@npm:^2.14.0":
version: 2.14.0
resolution: "twenty-sdk@npm:2.14.0"
dependencies:
"@sniptt/guards": "npm:^0.2.0"
axios: "npm:^1.16.0"
@@ -2835,12 +2917,12 @@ __metadata:
react: "npm:^19.2.0"
react-dom: "npm:^19.2.0"
tinyglobby: "npm:^0.2.15"
twenty-client-sdk: "npm:2.13.0"
twenty-client-sdk: "npm:2.14.0"
typescript: "npm:^5.9.3"
uuid: "npm:^13.0.2"
bin:
twenty: dist/cli.cjs
checksum: 10c0/51c350abe5d344fcad3c961a77632fd3cb2d91e357d0562b3eb02f05f66dbc41221c5463d0f91c022316a16af03a05ad43f038d9534c0ae31c060008d0e48672
checksum: 10c0/c4211772f8dd2ba81074a74be5f616c4c264d6bf8a174c5724be6d1c9a8cb3ca68a2674653bb58c059dae4bf1b0f43f91991e73d6a01395807206f0ac44afb08
languageName: node
linkType: hard