diff --git a/packages/create-twenty-app/src/constants/template/.oxlintrc.json b/packages/create-twenty-app/src/constants/template/.oxlintrc.json index 87c62c5183..369b9cfd76 100644 --- a/packages/create-twenty-app/src/constants/template/.oxlintrc.json +++ b/packages/create-twenty-app/src/constants/template/.oxlintrc.json @@ -15,5 +15,23 @@ } ], "typescript/no-explicit-any": "off" - } + }, + "overrides": [ + { + "files": ["**/*.logic-function.ts", "**/logic-functions/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["twenty-shared", "twenty-shared/*"], + "message": "Logic functions must not import from twenty-shared directly. Import runtime types and helpers from `twenty-sdk/logic-function` instead so the logic-function bundle stays minimal." + } + ] + } + ] + } + } + ] } diff --git a/packages/twenty-apps/community/github-connector/.oxlintrc.json b/packages/twenty-apps/community/github-connector/.oxlintrc.json index 87c62c5183..369b9cfd76 100644 --- a/packages/twenty-apps/community/github-connector/.oxlintrc.json +++ b/packages/twenty-apps/community/github-connector/.oxlintrc.json @@ -15,5 +15,23 @@ } ], "typescript/no-explicit-any": "off" - } + }, + "overrides": [ + { + "files": ["**/*.logic-function.ts", "**/logic-functions/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["twenty-shared", "twenty-shared/*"], + "message": "Logic functions must not import from twenty-shared directly. Import runtime types and helpers from `twenty-sdk/logic-function` instead so the logic-function bundle stays minimal." + } + ] + } + ] + } + } + ] } diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/contributor-stats.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/contributor-stats.logic-function.ts index cc49cc60ae..7555a3fb97 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/contributor-stats.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/contributor-stats.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { getClient } from 'src/modules/shared/twenty-client'; export type StatsPeriod = 'week' | 'month' | '3months' | 'year'; @@ -56,7 +57,11 @@ const PERIOD_CONFIG: Record< { granularity: Granularity; rangeMs: number; bucketCount: number } > = { week: { granularity: 'day', rangeMs: 7 * 24 * 3600 * 1000, bucketCount: 7 }, - month: { granularity: 'day', rangeMs: 30 * 24 * 3600 * 1000, bucketCount: 30 }, + month: { + granularity: 'day', + rangeMs: 30 * 24 * 3600 * 1000, + bucketCount: 30, + }, '3months': { granularity: 'week', rangeMs: 13 * 7 * 24 * 3600 * 1000, @@ -98,7 +103,10 @@ const bucketStartFor = (d: Date, granularity: Granularity): Date => { }; const formatBucketLabel = (start: Date, granularity: Granularity): string => { - const month = start.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' }); + const month = start.toLocaleString('en-US', { + month: 'short', + timeZone: 'UTC', + }); if (granularity === 'month') { return `${month} ${String(start.getUTCFullYear()).slice(2)}`; } diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/count-contributors.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/count-contributors.logic-function.ts index 9f291819fd..5704f40049 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/count-contributors.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/count-contributors.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { countAcrossRepos } from 'src/modules/github/connector/count-across-repos'; import { countContributors } from 'src/modules/github/contributor/graphql/github/count-contributors'; @@ -7,11 +8,7 @@ type CountContributorsPayload = { }; const handler = async (event: RoutePayload) => - countAcrossRepos( - event.body?.repos, - countContributors, - 'count-contributors', - ); + countAcrossRepos(event.body?.repos, countContributors, 'count-contributors'); export default defineLogicFunction({ universalIdentifier: 'fe0a6f00-0d63-4cb9-9b3c-1d8186181830', diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/fetch-contributors.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/fetch-contributors.logic-function.ts index 9233dbe56e..0a5128aa52 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/fetch-contributors.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/fetch-contributors.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { fetchContributors, type GqlContributor, @@ -41,7 +42,11 @@ const handler = async (event: RoutePayload) => { name: c.login, githubId: c.databaseId ?? 0, avatarUrl: c.avatarUrl - ? { primaryLinkLabel: c.login, primaryLinkUrl: c.avatarUrl, secondaryLinks: null } + ? { + primaryLinkLabel: c.login, + primaryLinkUrl: c.avatarUrl, + secondaryLinks: null, + } : null, })); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/search-contributors.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/search-contributors.logic-function.ts index 5074cfb65f..362fc64c04 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/search-contributors.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/search-contributors.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { searchContributors, type ContributorSearchResult, diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/top-contributors.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/top-contributors.logic-function.ts index 6395156d40..8c79aad18b 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/top-contributors.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/top-contributors.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { isBotLogin } from 'src/modules/github/contributor/utils/is-bot-login'; import { getClient } from 'src/modules/shared/twenty-client'; @@ -107,7 +108,10 @@ const tally = ( } } return Array.from(counts.values()) - .sort((a, b) => b.count - a.count || (a.ghLogin ?? '').localeCompare(b.ghLogin ?? '')) + .sort( + (a, b) => + b.count - a.count || (a.ghLogin ?? '').localeCompare(b.ghLogin ?? ''), + ) .slice(0, limit); }; @@ -148,78 +152,82 @@ const handler = async ( const client = getClient(); const authoredResult: { items: PrNode[]; truncated: boolean } = - kind === 'reviewers' ? { items: [], truncated: false } : await paginateUntil( - async (cursor) => { - const res = await client.query({ - pullRequests: { - __args: { - orderBy: [{ githubCreatedAt: 'DescNullsLast' }], - first: PAGE_SIZE, - after: cursor, - }, - edges: { - node: { - githubCreatedAt: true, - author: { - id: true, - name: true, - ghLogin: true, - avatarUrl: { primaryLinkUrl: true }, + kind === 'reviewers' + ? { items: [], truncated: false } + : await paginateUntil( + async (cursor) => { + const res = await client.query({ + pullRequests: { + __args: { + orderBy: [{ githubCreatedAt: 'DescNullsLast' }], + first: PAGE_SIZE, + after: cursor, + }, + edges: { + node: { + githubCreatedAt: true, + author: { + id: true, + name: true, + ghLogin: true, + avatarUrl: { primaryLinkUrl: true }, + }, + }, + }, + pageInfo: { hasNextPage: true, endCursor: true }, }, - }, + }); + return ( + (res.pullRequests as Connection) ?? { + edges: [], + pageInfo: { hasNextPage: false, endCursor: null }, + } + ); }, - pageInfo: { hasNextPage: true, endCursor: true }, - }, - }); - return ( - (res.pullRequests as Connection) ?? { - edges: [], - pageInfo: { hasNextPage: false, endCursor: null }, - } - ); - }, - (n) => { - if (!n.githubCreatedAt) return false; - return new Date(n.githubCreatedAt).getTime() < sinceMs; - }, - ); + (n) => { + if (!n.githubCreatedAt) return false; + return new Date(n.githubCreatedAt).getTime() < sinceMs; + }, + ); const reviewedResult: { items: ReviewNode[]; truncated: boolean } = - kind === 'authors' ? { items: [], truncated: false } : await paginateUntil( - async (cursor) => { - const res = await client.query({ - pullRequestReviews: { - __args: { - orderBy: [{ firstSubmittedAt: 'DescNullsLast' }], - first: PAGE_SIZE, - after: cursor, - }, - edges: { - node: { - firstSubmittedAt: true, - reviewer: { - id: true, - name: true, - ghLogin: true, - avatarUrl: { primaryLinkUrl: true }, + kind === 'authors' + ? { items: [], truncated: false } + : await paginateUntil( + async (cursor) => { + const res = await client.query({ + pullRequestReviews: { + __args: { + orderBy: [{ firstSubmittedAt: 'DescNullsLast' }], + first: PAGE_SIZE, + after: cursor, + }, + edges: { + node: { + firstSubmittedAt: true, + reviewer: { + id: true, + name: true, + ghLogin: true, + avatarUrl: { primaryLinkUrl: true }, + }, + }, + }, + pageInfo: { hasNextPage: true, endCursor: true }, }, - }, + }); + return ( + (res.pullRequestReviews as Connection) ?? { + edges: [], + pageInfo: { hasNextPage: false, endCursor: null }, + } + ); }, - pageInfo: { hasNextPage: true, endCursor: true }, - }, - }); - return ( - (res.pullRequestReviews as Connection) ?? { - edges: [], - pageInfo: { hasNextPage: false, endCursor: null }, - } - ); - }, - (n) => { - if (!n.firstSubmittedAt) return false; - return new Date(n.firstSubmittedAt).getTime() < sinceMs; - }, - ); + (n) => { + if (!n.firstSubmittedAt) return false; + return new Date(n.firstSubmittedAt).getTime() < sinceMs; + }, + ); const topAuthors = tally( authoredResult.items.map((pr) => ({ contributor: pr.author })), diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/count-issues.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/count-issues.logic-function.ts index 3fe155e805..90d264aae2 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/count-issues.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/count-issues.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { countAcrossRepos } from 'src/modules/github/connector/count-across-repos'; import { countIssues } from 'src/modules/github/issue/graphql/github/count-issues'; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/fetch-issues.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/fetch-issues.logic-function.ts index 3bd014f8b9..306cee67ea 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/fetch-issues.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/fetch-issues.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { fetchIssues, type GqlIssue, @@ -65,9 +66,8 @@ const handler = async (event: RoutePayload) => { authorId: issue.author ? (idByLogin.get(issue.author.login) ?? null) : null, })); - await timed( - `fetch-issues:upsertIssues ${tag} (${issueData.length})`, - () => batchUpsertIssues(issueData), + await timed(`fetch-issues:upsertIssues ${tag} (${issueData.length})`, () => + batchUpsertIssues(issueData), ); const totalMs = Date.now() - handlerStart; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/logic-functions/handle-webhook.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/logic-functions/handle-webhook.logic-function.ts index 2fd99f51df..fa2701169e 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/logic-functions/handle-webhook.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/logic-functions/handle-webhook.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import type { GitHubWebhookPayload } from 'src/modules/github/connector/webhook-payload'; import type { ProjectV2Item } from 'src/modules/github/project-item/types/project-v2-item'; import { fetchProjectItemByNodeId } from 'src/modules/github/project-item/graphql/github/fetch-project-item-by-node-id'; @@ -43,7 +44,9 @@ async function handlePullRequestEvent(payload: GitHubWebhookPayload) { const idByLogin = await upsertContributorsByLogin([pr.user, pr.merged_by]); const authorId = idByLogin.get(pr.user.login) ?? null; - const mergerId = pr.merged_by ? (idByLogin.get(pr.merged_by.login) ?? null) : null; + const mergerId = pr.merged_by + ? (idByLogin.get(pr.merged_by.login) ?? null) + : null; const canonical = pullRequestFromWebhook(pr, repository.full_name); @@ -81,7 +84,9 @@ async function handlePullRequestReviewEvent(payload: GitHubWebhookPayload) { { ...prCanonical, authorId: idByLogin.get(pr.user.login) ?? null, - mergerId: pr.merged_by ? (idByLogin.get(pr.merged_by.login) ?? null) : null, + mergerId: pr.merged_by + ? (idByLogin.get(pr.merged_by.login) ?? null) + : null, }, ]); @@ -175,7 +180,8 @@ async function handleProjectV2ItemEvent( return { skipped: true, reason: 'delete not implemented' }; } - const node = testProjectItem ?? (await fetchProjectItemByNodeId(item.node_id)); + const node = + testProjectItem ?? (await fetchProjectItemByNodeId(item.node_id)); if (!node) { return { skipped: true, reason: 'project item not found on GitHub' }; } diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/count-project-items.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/count-project-items.logic-function.ts index 1233032516..2b96b8ce5f 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/count-project-items.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/count-project-items.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { getGithubProjects, type GithubProject, diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/fetch-project-items.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/fetch-project-items.logic-function.ts index 712903c5a9..6222569935 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/fetch-project-items.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/fetch-project-items.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { fetchProjectItems } from 'src/modules/github/project-item/graphql/github/fetch-project-items'; import type { ProjectV2Item } from 'src/modules/github/project-item/types/project-v2-item'; import { batchUpsertProjectItems } from 'src/modules/github/project-item/graphql/mutations/batch-upsert'; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/logic-functions/recompute-pull-request-reviews.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/logic-functions/recompute-pull-request-reviews.logic-function.ts index 277a98879f..2151dea719 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/logic-functions/recompute-pull-request-reviews.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/logic-functions/recompute-pull-request-reviews.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { getClient } from 'src/modules/shared/twenty-client'; import { timed } from 'src/modules/shared/timing'; import { batchUpsertConsolidatedReviews } from 'src/modules/github/pull-request-review/graphql/mutations/batch-upsert'; @@ -128,9 +129,8 @@ const handler = async (_event: RoutePayload) => { let upsertedCount = 0; if (rows.length > 0) { - const recs = await timed( - `recompute-reviews:upsert (${rows.length})`, - () => batchUpsertConsolidatedReviews(rows), + const recs = await timed(`recompute-reviews:upsert (${rows.length})`, () => + batchUpsertConsolidatedReviews(rows), ); upsertedCount = recs.length; } diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/count-prs.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/count-prs.logic-function.ts index c8fb6424e2..86b0080855 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/count-prs.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/count-prs.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { countAcrossRepos } from 'src/modules/github/connector/count-across-repos'; import { countPullRequests } from 'src/modules/github/pull-request/graphql/github/count-pull-requests'; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/fetch-prs.logic-function.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/fetch-prs.logic-function.ts index 0d80f96e4e..e3d7d6777d 100644 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/fetch-prs.logic-function.ts +++ b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/fetch-prs.logic-function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { fetchPullRequests, type GqlPullRequest, @@ -51,7 +52,13 @@ const handler = async (event: RoutePayload) => { if (prs.length === 0) { console.log(`[fetch-prs] empty page for ${tag}`); - return { prCount: 0, reviewCount: 0, totalCount, hasMore: false, endCursor: null }; + return { + prCount: 0, + reviewCount: 0, + totalCount, + hasMore: false, + endCursor: null, + }; } const allUsers = prs.flatMap((pr) => [ diff --git a/packages/twenty-apps/examples/hello-world/.oxlintrc.json b/packages/twenty-apps/examples/hello-world/.oxlintrc.json index 87c62c5183..369b9cfd76 100644 --- a/packages/twenty-apps/examples/hello-world/.oxlintrc.json +++ b/packages/twenty-apps/examples/hello-world/.oxlintrc.json @@ -15,5 +15,23 @@ } ], "typescript/no-explicit-any": "off" - } + }, + "overrides": [ + { + "files": ["**/*.logic-function.ts", "**/logic-functions/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["twenty-shared", "twenty-shared/*"], + "message": "Logic functions must not import from twenty-shared directly. Import runtime types and helpers from `twenty-sdk/logic-function` instead so the logic-function bundle stays minimal." + } + ] + } + ] + } + } + ] } diff --git a/packages/twenty-apps/examples/hello-world/src/logic-functions/post-install.ts b/packages/twenty-apps/examples/hello-world/src/logic-functions/post-install.ts index e7a6b79a39..6ccefdccfa 100644 --- a/packages/twenty-apps/examples/hello-world/src/logic-functions/post-install.ts +++ b/packages/twenty-apps/examples/hello-world/src/logic-functions/post-install.ts @@ -1,6 +1,7 @@ -import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk/define'; +import { definePostInstallLogicFunction } from 'twenty-sdk/define'; +import { type InstallPayload } from 'twenty-sdk/logic-function'; -const handler = async (payload: InstallLogicFunctionPayload): Promise => { +const handler = async (payload: InstallPayload): Promise => { console.log( 'Post install logic function executed successfully!', payload.previousVersion, diff --git a/packages/twenty-apps/examples/hello-world/src/logic-functions/pre-install.ts b/packages/twenty-apps/examples/hello-world/src/logic-functions/pre-install.ts index a6ae0f1dca..58a92a9d81 100644 --- a/packages/twenty-apps/examples/hello-world/src/logic-functions/pre-install.ts +++ b/packages/twenty-apps/examples/hello-world/src/logic-functions/pre-install.ts @@ -1,7 +1,11 @@ -import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk/define'; +import { definePreInstallLogicFunction } from 'twenty-sdk/define'; +import { type InstallPayload } from 'twenty-sdk/logic-function'; -const handler = async (payload: InstallLogicFunctionPayload): Promise => { - console.log('Pre install logic function executed successfully!', payload.previousVersion); +const handler = async (payload: InstallPayload): Promise => { + console.log( + 'Pre install logic function executed successfully!', + payload.previousVersion, + ); }; export default definePreInstallLogicFunction({ diff --git a/packages/twenty-apps/examples/postcard/.oxlintrc.json b/packages/twenty-apps/examples/postcard/.oxlintrc.json index 87c62c5183..369b9cfd76 100644 --- a/packages/twenty-apps/examples/postcard/.oxlintrc.json +++ b/packages/twenty-apps/examples/postcard/.oxlintrc.json @@ -15,5 +15,23 @@ } ], "typescript/no-explicit-any": "off" - } + }, + "overrides": [ + { + "files": ["**/*.logic-function.ts", "**/logic-functions/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["twenty-shared", "twenty-shared/*"], + "message": "Logic functions must not import from twenty-shared directly. Import runtime types and helpers from `twenty-sdk/logic-function` instead so the logic-function bundle stays minimal." + } + ] + } + ] + } + } + ] } diff --git a/packages/twenty-apps/internal/exa/src/logic-functions/schemas/exa-web-search-input.schema.ts b/packages/twenty-apps/internal/exa/src/logic-functions/schemas/exa-web-search-input.schema.ts index dcd8b1ea58..c587302765 100644 --- a/packages/twenty-apps/internal/exa/src/logic-functions/schemas/exa-web-search-input.schema.ts +++ b/packages/twenty-apps/internal/exa/src/logic-functions/schemas/exa-web-search-input.schema.ts @@ -1,4 +1,4 @@ -import { type InputJsonSchema } from 'twenty-shared/logic-function'; +import { type InputJsonSchema } from 'twenty-sdk/logic-function'; import { DEFAULT_NUM_RESULTS } from '../constants/default-num-results.constant'; import { EXA_CATEGORIES } from '../constants/exa-categories.constant'; diff --git a/packages/twenty-apps/internal/self-hosting/src/logic-functions/telemetryWebhook.function.ts b/packages/twenty-apps/internal/self-hosting/src/logic-functions/telemetryWebhook.function.ts index 453679dfc5..2df303be07 100644 --- a/packages/twenty-apps/internal/self-hosting/src/logic-functions/telemetryWebhook.function.ts +++ b/packages/twenty-apps/internal/self-hosting/src/logic-functions/telemetryWebhook.function.ts @@ -1,4 +1,5 @@ -import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { type RoutePayload } from 'twenty-sdk/logic-function'; import { CoreApiClient } from 'twenty-client-sdk/core'; import { type TelemetryEvent } from 'src/logic-functions/types/telemetry-event.type'; diff --git a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-created.ts b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-created.ts index ee55dc6d09..14aafbb3ff 100644 --- a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-created.ts +++ b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-created.ts @@ -1,6 +1,10 @@ import { isNonEmptyString } from '@sniptt/guards'; import { CoreApiClient } from 'twenty-client-sdk/core'; -import { defineLogicFunction, type DatabaseEventPayload, type ObjectRecordCreateEvent } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { + type DatabaseEventPayload, + type ObjectRecordCreateEvent, +} from 'twenty-sdk/logic-function'; import { isDefined } from '@utils/is-defined'; import { ON_RESEND_CONTACT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers'; @@ -18,7 +22,10 @@ const handler = async ( const { after } = event.properties; if (isNonEmptyString(after.resendId)) { - return { skipped: true, reason: 'record already has resendId (inbound sync)' }; + return { + skipped: true, + reason: 'record already has resendId (inbound sync)', + }; } const email = after.email?.primaryEmail; @@ -63,11 +70,17 @@ const handler = async ( }, }); - return { synced: true, resendId: data.id, twentyId: event.recordId, personId }; + return { + synced: true, + resendId: data.id, + twentyId: event.recordId, + personId, + }; }; export default defineLogicFunction({ - universalIdentifier: ON_RESEND_CONTACT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + universalIdentifier: + ON_RESEND_CONTACT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, name: 'on-resend-contact-created', description: 'Creates a contact in Resend when a new resendContact record is created in Twenty', diff --git a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-destroyed.ts b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-destroyed.ts index f46bb10412..c0f422d23f 100644 --- a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-destroyed.ts +++ b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-destroyed.ts @@ -1,5 +1,9 @@ import { isNonEmptyString } from '@sniptt/guards'; -import { defineLogicFunction, type DatabaseEventPayload, type ObjectRecordDestroyEvent } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { + type DatabaseEventPayload, + type ObjectRecordDestroyEvent, +} from 'twenty-sdk/logic-function'; import { isDefined } from '@utils/is-defined'; import { ON_RESEND_CONTACT_DESTROYED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers'; @@ -39,7 +43,8 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: ON_RESEND_CONTACT_DESTROYED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + universalIdentifier: + ON_RESEND_CONTACT_DESTROYED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, name: 'on-resend-contact-destroyed', description: 'Removes a contact from Resend when a resendContact record is permanently destroyed in Twenty', diff --git a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-updated.ts b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-updated.ts index b0efd26fc7..f881453c21 100644 --- a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-updated.ts +++ b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-contact-updated.ts @@ -1,6 +1,10 @@ import { isNonEmptyString } from '@sniptt/guards'; import { CoreApiClient } from 'twenty-client-sdk/core'; -import { defineLogicFunction, type DatabaseEventPayload, type ObjectRecordUpdateEvent } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { + type DatabaseEventPayload, + type ObjectRecordUpdateEvent, +} from 'twenty-sdk/logic-function'; import { isDefined } from '@utils/is-defined'; import { ON_RESEND_CONTACT_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers'; @@ -106,7 +110,8 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: ON_RESEND_CONTACT_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + universalIdentifier: + ON_RESEND_CONTACT_UPDATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, name: 'on-resend-contact-updated', description: 'Pushes contact field changes to Resend when a resendContact record is updated in Twenty', diff --git a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-created.ts b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-created.ts index 0c7cbb62b6..2e841b4c12 100644 --- a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-created.ts +++ b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-created.ts @@ -1,6 +1,10 @@ import { isNonEmptyString } from '@sniptt/guards'; import { CoreApiClient } from 'twenty-client-sdk/core'; -import { defineLogicFunction, type DatabaseEventPayload, type ObjectRecordCreateEvent } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { + type DatabaseEventPayload, + type ObjectRecordCreateEvent, +} from 'twenty-sdk/logic-function'; import { ON_RESEND_SEGMENT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers'; import type { ResendSegmentRecord } from '@modules/resend/shared/types/resend-segment-record'; @@ -17,7 +21,10 @@ const handler = async ( const { after } = event.properties; if (isNonEmptyString(after.resendId)) { - return { skipped: true, reason: 'record already has resendId (inbound sync)' }; + return { + skipped: true, + reason: 'record already has resendId (inbound sync)', + }; } const name = after.name; @@ -48,7 +55,8 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: ON_RESEND_SEGMENT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + universalIdentifier: + ON_RESEND_SEGMENT_CREATED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, name: 'on-resend-segment-created', description: 'Creates a segment in Resend when a new resendSegment record is created in Twenty', diff --git a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-destroyed.ts b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-destroyed.ts index 2e76516337..27eee82de6 100644 --- a/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-destroyed.ts +++ b/packages/twenty-apps/internal/twenty-for-twenty/src/modules/resend/sync/logic-functions/on-resend-segment-destroyed.ts @@ -1,5 +1,9 @@ import { isNonEmptyString } from '@sniptt/guards'; -import { defineLogicFunction, type DatabaseEventPayload, type ObjectRecordDestroyEvent } from 'twenty-sdk/define'; +import { defineLogicFunction } from 'twenty-sdk/define'; +import { + type DatabaseEventPayload, + type ObjectRecordDestroyEvent, +} from 'twenty-sdk/logic-function'; import { isDefined } from '@utils/is-defined'; import { ON_RESEND_SEGMENT_DESTROYED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from '@modules/resend/constants/universal-identifiers'; @@ -39,7 +43,8 @@ const handler = async ( }; export default defineLogicFunction({ - universalIdentifier: ON_RESEND_SEGMENT_DESTROYED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, + universalIdentifier: + ON_RESEND_SEGMENT_DESTROYED_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER, name: 'on-resend-segment-destroyed', description: 'Removes a segment from Resend when a resendSegment record is permanently destroyed in Twenty', diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index d6e9653870..319191d372 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -37,6 +37,11 @@ "import": "./dist/front-component/index.mjs", "require": "./dist/front-component/index.cjs" }, + "./logic-function": { + "types": "./dist/logic-function/index.d.ts", + "import": "./dist/logic-function/index.mjs", + "require": "./dist/logic-function/index.cjs" + }, "./ui": { "types": "./dist/ui/index.d.ts", "import": "./dist/ui/index.mjs", @@ -111,6 +116,9 @@ "front-component": [ "dist/front-component/index.d.ts" ], + "logic-function": [ + "dist/logic-function/index.d.ts" + ], "cli": [ "dist/cli/operations/index.d.ts" ], diff --git a/packages/twenty-sdk/project.json b/packages/twenty-sdk/project.json index 4fa38db83f..6606d4f235 100644 --- a/packages/twenty-sdk/project.json +++ b/packages/twenty-sdk/project.json @@ -14,9 +14,9 @@ "options": { "cwd": "{projectRoot}", "commands": [ - "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts", + "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.logic-function.ts && npx vite build -c vite.config.browser.ts", "tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist", - "npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs" + "npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' 'dist/logic-function/**/*.d.ts' 'dist/logic-function/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs" ], "parallel": false } @@ -26,7 +26,7 @@ "dependsOn": ["^build"], "options": { "cwd": "packages/twenty-sdk", - "command": "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.browser.ts && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist && npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs && npx vite build -c vite.config.node.ts --watch & npx vite build -c vite.config.define.ts --watch & npx vite build -c vite.config.billing.ts --watch & npx vite build -c vite.config.front-component.ts --watch & npx vite build -c vite.config.browser.ts --watch" + "command": "npx rimraf dist && npx vite build -c vite.config.node.ts && npx vite build -c vite.config.define.ts && npx vite build -c vite.config.billing.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.logic-function.ts && npx vite build -c vite.config.browser.ts && tsgo -p tsconfig.lib.json --declaration --emitDeclarationOnly --noEmit false --outDir dist --rootDir src && npx tsc-alias -p tsconfig.lib.json --outDir dist && npx rimraf 'dist/sdk' 'dist/define/**/*.d.ts' 'dist/define/**/*.d.ts.map' 'dist/billing/**/*.d.ts' 'dist/billing/**/*.d.ts.map' 'dist/front-component/**/*.d.ts' 'dist/front-component/**/*.d.ts.map' 'dist/logic-function/**/*.d.ts' 'dist/logic-function/**/*.d.ts.map' && npx rollup -c rollup.config.sdk-dts.mjs && npx vite build -c vite.config.node.ts --watch & npx vite build -c vite.config.define.ts --watch & npx vite build -c vite.config.billing.ts --watch & npx vite build -c vite.config.front-component.ts --watch & npx vite build -c vite.config.logic-function.ts --watch & npx vite build -c vite.config.browser.ts --watch" } }, "start": { @@ -93,11 +93,12 @@ "inputs": ["{projectRoot}/src/sdk/**/*"], "outputs": [ "{projectRoot}/dist/define", - "{projectRoot}/dist/front-component" + "{projectRoot}/dist/front-component", + "{projectRoot}/dist/logic-function" ], "options": { "cwd": "{projectRoot}", - "command": "npx vite build -c vite.config.define.ts && npx vite build -c vite.config.front-component.ts && npx rollup -c rollup.config.sdk-dts.mjs" + "command": "npx vite build -c vite.config.define.ts && npx vite build -c vite.config.front-component.ts && npx vite build -c vite.config.logic-function.ts && npx rollup -c rollup.config.sdk-dts.mjs" } } } diff --git a/packages/twenty-sdk/rollup.config.sdk-dts.mjs b/packages/twenty-sdk/rollup.config.sdk-dts.mjs index e1bdfd5d3b..666d6d9fd0 100644 --- a/packages/twenty-sdk/rollup.config.sdk-dts.mjs +++ b/packages/twenty-sdk/rollup.config.sdk-dts.mjs @@ -36,4 +36,10 @@ export default [ external, plugins, }, + { + input: 'src/sdk/logic-function/index.ts', + output: { file: 'dist/logic-function/index.d.ts', format: 'es' }, + external, + plugins, + }, ]; diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts b/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts index 5b453c6d99..cd38cda79f 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/build-application.ts @@ -12,6 +12,7 @@ import { esbuildOneShotBuild } from '@/cli/utilities/build/common/esbuild-one-sh import { LOGIC_FUNCTION_EXTERNAL_MODULES } from '@/cli/utilities/build/common/esbuild-watcher'; import { getBaseFrontComponentBuildOptions } from '@/cli/utilities/build/common/front-component-build/utils/get-base-front-component-build-options'; import { getFrontComponentBuildPlugins } from '@/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins'; +import { createStubTwentySdkDefinePlugin } from '@/cli/utilities/build/common/plugins/stub-twenty-sdk-define.plugin'; import { type OnFileBuiltCallback } from '@/cli/utilities/build/common/restartable-watcher-interface'; import { type EntityFilePaths } from '@/cli/utilities/build/manifest/manifest-extract-config'; import { @@ -79,6 +80,7 @@ export const buildApplication = async ( metafile: true, logLevel: 'silent', banner: NODE_ESM_CJS_BANNER, + plugins: [createStubTwentySdkDefinePlugin()], }, onFileBuilt: collectFileBuilt, }); @@ -95,7 +97,10 @@ export const buildApplication = async ( sourcemap: true, metafile: true, logLevel: 'silent', - plugins: [...getFrontComponentBuildPlugins()], + plugins: [ + ...getFrontComponentBuildPlugins(), + createStubTwentySdkDefinePlugin(), + ], }, onFileBuilt: collectFileBuilt, }); diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts b/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts index 32831c8d3f..c3666f3eae 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/common/esbuild-watcher.ts @@ -2,6 +2,7 @@ import { cleanupRemovedFiles } from '@/cli/utilities/build/common/cleanup-remove import { processEsbuildResult } from '@/cli/utilities/build/common/esbuild-result-processor'; import { FRONT_COMPONENT_EXTERNAL_MODULES } from '@/cli/utilities/build/common/front-component-build/constants/front-component-external-modules'; import { getFrontComponentBuildPlugins } from '@/cli/utilities/build/common/front-component-build/utils/get-front-component-build-plugins'; +import { createStubTwentySdkDefinePlugin } from '@/cli/utilities/build/common/plugins/stub-twenty-sdk-define.plugin'; import { type OnBuildErrorCallback, type OnFileBuiltCallback, @@ -202,6 +203,7 @@ export const createLogicFunctionsWatcher = ( platform: 'node', extraPlugins: [ createTypecheckPlugin(options.appPath, options.shouldSkipTypecheck), + createStubTwentySdkDefinePlugin(), ], banner: NODE_ESM_CJS_BANNER, }, @@ -219,6 +221,7 @@ export const createFrontComponentsWatcher = ( extraPlugins: [ createTypecheckPlugin(options.appPath, options.shouldSkipTypecheck), ...getFrontComponentBuildPlugins(), + createStubTwentySdkDefinePlugin(), ], }, }); diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap new file mode 100644 index 0000000000..c860e4176b --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap @@ -0,0 +1,49 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1`] = ` +{ + "any": [ + "AggregateOperations", + "DateDisplayFormat", + "FieldMetadataSettingsOnClickAction", + "FieldType", + "HTTPMethod", + "NavigationMenuItemType", + "NumberDataType", + "ObjectRecordGroupByDateGranularity", + "OnDeleteAction", + "PageLayoutTabLayoutMode", + "PermissionFlag", + "RelationType", + "STANDARD_OBJECT", + "STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS", + "ViewFilterGroupLogicalOperator", + "ViewFilterOperand", + "ViewKey", + "ViewOpenRecordIn", + "ViewSortDirection", + "ViewType", + "ViewVisibility", + "generateDefaultFieldUniversalIdentifier", + "getPublicAssetUrl", + "validateFields", + ], + "factories": [ + "createValidationResult", + "defineAgent", + "defineApplication", + "defineField", + "defineFrontComponent", + "defineLogicFunction", + "defineNavigationMenuItem", + "defineObject", + "definePageLayout", + "definePageLayoutTab", + "definePostInstallLogicFunction", + "definePreInstallLogicFunction", + "defineRole", + "defineSkill", + "defineView", + ], +} +`; diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/stub-twenty-sdk-define.plugin.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/stub-twenty-sdk-define.plugin.spec.ts new file mode 100644 index 0000000000..f6f299e1dc --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/stub-twenty-sdk-define.plugin.spec.ts @@ -0,0 +1,42 @@ +import * as twentySdkDefine from '@/sdk/define'; +import { + TWENTY_SDK_DEFINE_STUBBED_EXPORTS, + isDefineFactoryExportName, +} from '@/cli/utilities/build/common/plugins/stub-twenty-sdk-define.plugin'; + +describe('stub-twenty-sdk-define plugin', () => { + const realExports = Object.keys(twentySdkDefine).sort(); + const stubbedExports = [ + ...TWENTY_SDK_DEFINE_STUBBED_EXPORTS.factories, + ...TWENTY_SDK_DEFINE_STUBBED_EXPORTS.any, + ].sort(); + + it('classifies every twenty-sdk/define value-export', () => { + expect(stubbedExports).toEqual(realExports); + }); + + it('classifies all defineX exports (and createValidationResult) as factories', () => { + const expectedFactories = realExports + .filter(isDefineFactoryExportName) + .sort(); + + expect([...TWENTY_SDK_DEFINE_STUBBED_EXPORTS.factories].sort()).toEqual( + expectedFactories, + ); + }); + + it('every factory is callable in the real module (would-be misclassification guard)', () => { + for (const name of TWENTY_SDK_DEFINE_STUBBED_EXPORTS.factories) { + const actual = (twentySdkDefine as unknown as Record)[ + name + ]; + expect(typeof actual).toBe('function'); + } + }); + + // Snapshot to surface new exports in PR review. Update with + // `npx vitest -u` when intentional. + it('matches the recorded export partition', () => { + expect(TWENTY_SDK_DEFINE_STUBBED_EXPORTS).toMatchSnapshot(); + }); +}); diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/stub-twenty-sdk-define.plugin.ts b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/stub-twenty-sdk-define.plugin.ts new file mode 100644 index 0000000000..8893d51fd1 --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/stub-twenty-sdk-define.plugin.ts @@ -0,0 +1,90 @@ +import type * as esbuild from 'esbuild'; + +import * as twentySdkDefine from '@/sdk/define'; + +// Everything in twenty-sdk/define is build-time metadata for the manifest +// extractor and is dead code in the user-app bundle. Stubbing it drops zod, +// twenty-shared and ~1MB per logic-function bundle. +// +// Exports are derived from `Object.keys(twentySdkDefine)` so new ones land +// in the stub automatically; the unit test snapshots the partition. +// Factories must keep the `(config) => ({ config, ... })` shape so the +// manifest extractor can still read `.config`; anything else can be a Proxy. +export const isDefineFactoryExportName = (name: string): boolean => + /^define[A-Z]/.test(name) || name === 'createValidationResult'; + +const partitionDefineExports = ( + mod: Record, +): { factories: readonly string[]; any: readonly string[] } => { + const factories: string[] = []; + const any: string[] = []; + + for (const name of Object.keys(mod).sort()) { + if (isDefineFactoryExportName(name)) { + factories.push(name); + } else { + any.push(name); + } + } + + return { factories, any }; +}; + +export const TWENTY_SDK_DEFINE_STUBBED_EXPORTS = partitionDefineExports( + twentySdkDefine as unknown as Record, +); + +const VIRTUAL_NAMESPACE = 'twenty-sdk-define-stub'; +const STUB_RESOLVED_PATH = '__twenty-sdk-define-stub__'; + +const STUB_PRELUDE = ` +// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build. +// Real implementations would pull in zod, twenty-shared and ~1MB of code; at +// runtime only \`default.config.handler\` is consumed, so tiny no-ops suffice. +const __defineFactoryStub = (config) => ({ + success: true, + config, + errors: [], +}); + +const __anyHandler = { + get(_target, prop) { + if (prop === '__esModule') return true; + if (prop === Symbol.toPrimitive) return () => ''; + if (typeof prop === 'symbol') return undefined; + return new Proxy(() => undefined, __anyHandler); + }, + apply() { + return new Proxy(() => undefined, __anyHandler); + }, +}; +const __anyStub = new Proxy(() => undefined, __anyHandler); +`; + +const buildStubModuleSource = (): string => { + const exportLines: string[] = []; + + for (const name of TWENTY_SDK_DEFINE_STUBBED_EXPORTS.factories) { + exportLines.push(`export const ${name} = __defineFactoryStub;`); + } + for (const name of TWENTY_SDK_DEFINE_STUBBED_EXPORTS.any) { + exportLines.push(`export const ${name} = __anyStub;`); + } + + return `${STUB_PRELUDE}\n${exportLines.join('\n')}\n`; +}; + +export const createStubTwentySdkDefinePlugin = (): esbuild.Plugin => ({ + name: 'twenty-sdk-define-stub', + setup(build) { + build.onResolve({ filter: /^twenty-sdk\/define$/ }, () => ({ + path: STUB_RESOLVED_PATH, + namespace: VIRTUAL_NAMESPACE, + })); + + build.onLoad({ filter: /.*/, namespace: VIRTUAL_NAMESPACE }, () => ({ + contents: buildStubModuleSource(), + loader: 'js', + })); + }, +}); diff --git a/packages/twenty-sdk/src/sdk/logic-function/index.ts b/packages/twenty-sdk/src/sdk/logic-function/index.ts new file mode 100644 index 0000000000..4b238575cc --- /dev/null +++ b/packages/twenty-sdk/src/sdk/logic-function/index.ts @@ -0,0 +1,39 @@ +// Runtime-facing barrel for logic-function authors. +// +// Anything imported from this entry point is allowed to reach the Lambda +// runtime. Today we only re-export type-only payload shapes; all of these +// disappear at TS compile time so the compiled bundle is empty. +// +// `defineLogicFunction`, `definePostInstallLogicFunction`, etc. intentionally +// stay in `twenty-sdk/define` — they are build-time metadata factories that +// the SDK CLI stubs out before bundling. Logic-function source files keep +// importing them from `twenty-sdk/define`, but should reach for *types* (and +// future runtime helpers) here, never directly from `twenty-shared/*`. + +export type { + LogicFunctionConfig, + LogicFunctionHandler, +} from '@/sdk/define/logic-functions/logic-function-config'; + +export type { + InstallHandler, + InstallPayload, +} from '@/sdk/define/logic-functions/install-payload-type'; + +export type { CronPayload } from '@/sdk/define/logic-functions/triggers/cron-payload-type'; + +export type { + DatabaseEventPayload, + ObjectRecordBaseEvent, + ObjectRecordCreateEvent, + ObjectRecordDeleteEvent, + ObjectRecordDestroyEvent, + ObjectRecordEvent, + ObjectRecordRestoreEvent, + ObjectRecordUpdateEvent, + ObjectRecordUpsertEvent, +} from '@/sdk/define/logic-functions/triggers/database-event-payload-type'; + +export type { RoutePayload } from '@/sdk/define/logic-functions/triggers/route-payload-type'; + +export type { InputJsonSchema } from 'twenty-shared/logic-function'; diff --git a/packages/twenty-sdk/vite.config.logic-function.ts b/packages/twenty-sdk/vite.config.logic-function.ts new file mode 100644 index 0000000000..2b7e023416 --- /dev/null +++ b/packages/twenty-sdk/vite.config.logic-function.ts @@ -0,0 +1,65 @@ +import path from 'path'; +import { type PackageJson } from 'type-fest'; +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +import packageJson from './package.json'; + +export default defineConfig(() => { + return { + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/twenty-sdk-logic-function', + resolve: { + alias: { + '@/': path.resolve(__dirname, 'src') + '/', + }, + }, + plugins: [ + tsconfigPaths({ + root: __dirname, + }), + ], + build: { + emptyOutDir: false, + outDir: 'dist/logic-function', + sourcemap: true, + lib: { + entry: 'src/sdk/logic-function/index.ts', + name: 'twenty-sdk-logic-function', + formats: ['es', 'cjs'], + fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`, + }, + rollupOptions: { + external: (id: string) => { + if (/^node:/.test(id)) { + return true; + } + + const builtins = [ + 'child_process', + 'crypto', + 'fs', + 'fs/promises', + 'module', + 'os', + 'path', + 'stream', + 'url', + 'util', + ]; + + if (builtins.includes(id)) { + return true; + } + + const deps = Object.keys( + (packageJson as PackageJson).dependencies || {}, + ); + + return deps.some((dep) => id === dep || id.startsWith(dep + '/')); + }, + }, + }, + logLevel: 'warn' as const, + }; +});