diff --git a/packages/twenty-apps/community/github-connector/.gitignore b/packages/twenty-apps/community/github-connector/.gitignore deleted file mode 100644 index 5fc90b42ab..0000000000 --- a/packages/twenty-apps/community/github-connector/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn - -# codegen -generated - -# dev -/dist/ -.twenty - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files -.env* - -# typescript -*.tsbuildinfo -*.d.ts diff --git a/packages/twenty-apps/community/github-connector/.nvmrc b/packages/twenty-apps/community/github-connector/.nvmrc deleted file mode 100644 index 341cb50613..0000000000 --- a/packages/twenty-apps/community/github-connector/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.5.0 diff --git a/packages/twenty-apps/community/github-connector/.oxlintrc.json b/packages/twenty-apps/community/github-connector/.oxlintrc.json deleted file mode 100644 index 369b9cfd76..0000000000 --- a/packages/twenty-apps/community/github-connector/.oxlintrc.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["typescript"], - "categories": { - "correctness": "off" - }, - "ignorePatterns": ["node_modules", "dist"], - "rules": { - "no-unused-vars": "off", - - "typescript/no-unused-vars": [ - "warn", - { - "argsIgnorePattern": "^_" - } - ], - "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/.yarnrc.yml b/packages/twenty-apps/community/github-connector/.yarnrc.yml deleted file mode 100644 index 3186f3f079..0000000000 --- a/packages/twenty-apps/community/github-connector/.yarnrc.yml +++ /dev/null @@ -1 +0,0 @@ -nodeLinker: node-modules diff --git a/packages/twenty-apps/community/github-connector/README.md b/packages/twenty-apps/community/github-connector/README.md deleted file mode 100644 index 0b2e68c854..0000000000 --- a/packages/twenty-apps/community/github-connector/README.md +++ /dev/null @@ -1,254 +0,0 @@ -# GitHub Connector - -Sync pull requests, issues, contributors and project items from GitHub into -Twenty, and react to GitHub webhook events in real time. - -This app showcases how to build a non-trivial third-party connector with the -Twenty SDK: custom objects with rich relationships, navigation menu items, -table views, a dashboard page layout, logic functions for periodic syncs, an -HTTP webhook handler, and authenticated GraphQL/REST calls against an -external provider. - -![GitHub Connector marketplace listing in Settings → Apps with About / Content / Permissions / Settings tabs](public/screenshots/app-listing.png) - -![GitHub Dashboard with PR / review counters, weekly histograms, and top-contributor leaderboards](public/screenshots/github-dashboard.png) - -![Pull Requests view with the Fetch Pull Requests command](public/screenshots/pull-requests-view.png) - -![Contributor detail page with the Contributor Stats panel showing PRs authored, merged and reviewed over time](public/screenshots/contributor-stats.png) - -## What it adds to your workspace - -### Custom objects - -Six custom objects, each with fields, relationships and table views: - -- `pullRequest` -- `pullRequestReview` -- `pullRequestReviewEvent` -- `issue` -- `projectItem` -- `contributor` - -### Navigation - -A top-level **GitHub** folder in the left sidebar with: - -- Pull Requests -- Issues -- Project Items -- Contributors -- Pull Request Reviews -- Pull Request Review Events -- GitHub Dashboard (a page layout that aggregates PR activity over time and - surfaces top contributors) - -### Logic functions - -| Function | Trigger | -| --------------------------------- | ------------------------------------------------ | -| `count-prs` | HTTP `POST /github/count-prs` | -| `fetch-prs` | HTTP `POST /github/fetch-prs` | -| `count-issues` | HTTP `POST /github/count-issues` | -| `fetch-issues` | HTTP `POST /github/fetch-issues` | -| `count-contributors` | HTTP `POST /github/count-contributors` | -| `fetch-contributors` | HTTP `POST /github/fetch-contributors` | -| `count-project-items` | HTTP `POST /github/count-project-items` | -| `fetch-project-items` | HTTP `POST /github/fetch-project-items` | -| `handle-github-webhook` | HTTP `POST /github/webhook` (no auth, signed) | -| `search-contributors` | HTTP `POST /contributors/search` | -| `contributor-stats` | HTTP `POST /contributors/stats` | -| `top-contributors` | HTTP `POST /contributors/top` | -| `recompute-pull-request-reviews` | HTTP `POST /pull-request-reviews/recompute` | - -### Front components - -Seven front components surface the connector inside the Twenty UI: - -- **Fetch Pull Requests** — command on the Pull Request object -- **Fetch Issues** — command on the Issue object -- **Fetch Contributors** — command on the Contributor object -- **Fetch Project Items** — command on the Project Item object -- **Contributor Stats** — panel on the Contributor object that renders a - bar chart of PRs authored / merged / reviewed over the selected period -- **Top PR Authors** — dashboard widget that ranks the top 20 PR authors - over the last 90 days -- **Top Reviewers** — dashboard widget that ranks the top 20 PR reviewers - over the last 90 days - -## Install - -You have two options. Use **dev mode** for a tight edit/test loop while -iterating on the app, or **install** for a one-shot deploy. - -### Option A — Live development (`yarn twenty dev`) - -Use this when you want every code change to be re-synced into your local -Twenty server automatically. - -```bash -cd packages/twenty-apps/community/github-connector -yarn install - -# Register your local Twenty server as a remote (interactive prompt). -# When asked for the URL use http://localhost:2021 and paste an API key -# from Settings -> Developers in the Twenty UI. -yarn twenty remote:add - -# Build, install, and watch for changes. -yarn twenty dev -``` - -The first `yarn twenty dev` run installs the app on the remote and starts -watching `src/`. Edit any file and the change is re-synced within seconds. - -### Option B — One-shot install - -```bash -cd packages/twenty-apps/community/github-connector -yarn install -yarn twenty remote:add # same prompts as above -yarn twenty app:install # builds and installs once -``` - -## Configure authentication - -Once the app is installed, open the Twenty UI and go to -**Settings → Apps → GitHub Connector**. You only need one of the two auth -methods. - -### Option 1 — Personal Access Token (recommended for trying it out) - -| Variable | Required | Notes | -| --------------- | -------- | -------------------------------------------------------------------- | -| `GITHUB_TOKEN` | yes | Fine-grained PAT (`github_pat_…`). See permissions below. | - -Create a fine-grained PAT at -: - -1. **Resource owner**: the org (or user) that owns the repos in - `GITHUB_REPOS` and the projects in `GITHUB_PROJECTS`. Org-owned tokens - must be approved by an org admin before they can read org resources. -2. **Repository access**: pick the specific repos (or "All repositories"). -3. **Repository permissions** — set to **Read-only**: - - `Contents` - - `Issues` - - `Pull requests` - - `Metadata` (selected automatically) -4. **Organization permissions** — only if you want to sync GitHub Projects - (v2): set `Projects` to **Read-only**. -5. Generate, then copy the `github_pat_…` value. - -Classic PATs are intentionally not supported — fine-grained tokens are -scoped per-repo/per-org and avoid the all-or-nothing `repo` scope. - -When `GITHUB_TOKEN` is set, it always wins regardless of any GitHub App -config below. - -### Option 2 — GitHub App (recommended for production / org-wide installs) - -| Variable | Required | Notes | -| ---------------------------- | -------- | ---------------------------------------------------------------------- | -| `GITHUB_APP_ID` | yes | Numeric App ID from the GitHub App settings page. | -| `GITHUB_APP_PRIVATE_KEY` | yes | PEM private key (BEGIN/END PRIVATE KEY block). Newlines are tolerant. | -| `GITHUB_APP_INSTALLATION_ID` | yes | The installation id of the App on your org/user. | - -To create one: - -1. (or - `https://github.com/organizations//settings/apps/new`). -2. Grant the App these **repository permissions**: `Contents: Read`, - `Issues: Read`, `Pull Requests: Read`, `Metadata: Read`. For Projects v2 - add `Organization → Projects: Read`. -3. Generate a private key (downloads a `.pem`). -4. Install the App on your org/user — the URL bar of the post-install page - contains the installation id, e.g. `.../installations/12345678`. -5. Paste the App ID, the PEM contents, and the installation ID into the - variables above. - -The connector exchanges the App credentials for a short-lived installation -token (cached in-memory until shortly before expiry) and uses it for all -GitHub calls. - -### Common variables - -| Variable | Required | Notes | -| ------------------------- | -------- | --------------------------------------------------------------------------------------- | -| `GITHUB_REPOS` | yes | Comma-separated `owner/repo` list, e.g. `octocat/hello-world,octo-org/octo-repo`. | -| `GITHUB_PROJECTS` | no | Comma-separated GitHub Projects (v2). See format below. | -| `GITHUB_WEBHOOK_SECRET` | no | Shared secret to verify `X-Hub-Signature-256`. When unset, signatures are not verified. | - -`GITHUB_PROJECTS` accepts entries in either of these forms: - -- `owner/number` — e.g. `twentyhq/24,octo/3`. Owner can be an org or a user. -- Full project URL — e.g. `https://github.com/orgs/twentyhq/projects/24` or - `https://github.com/users/octocat/projects/3`. - -## Running a sync - -In the Twenty UI, open any of the GitHub objects (e.g. **Pull Requests**) and -trigger the matching command from the command palette (`Cmd/Ctrl+K`): - -| View | Command | Reads from | -| ----------------- | --------------------- | ----------------- | -| Pull Requests | Fetch Pull Requests | `GITHUB_REPOS` | -| Issues | Fetch Issues | `GITHUB_REPOS` | -| Contributors | Fetch Contributors | `GITHUB_REPOS` | -| Project Items | Fetch Project Items | `GITHUB_PROJECTS` | - -Each command iterates over every entry in the relevant variable and shows a -progress bar. - -## Webhooks - -Point a GitHub repo or App webhook at the public URL of your Twenty server, -path `POST /github/webhook`. Recommended event subscriptions: - -- Pull requests -- Pull request reviews -- Issues -- Project (v2) items - -Set the same value as `GITHUB_WEBHOOK_SECRET` on both sides to enable HMAC -verification. For local testing, expose your dev server with -[smee.io](https://smee.io/) or `ngrok` and use that URL as the webhook URL on -GitHub. - -> **Heads up — raw body required for signatures.** HMAC verification needs -> the original request body bytes. The Twenty SDK currently parses JSON -> requests before handing them to logic functions, so when the runtime -> delivers an already-parsed body the connector logs a warning and rejects -> the delivery instead of silently accepting it. Until the SDK exposes the -> raw bytes for HTTP routes, either leave `GITHUB_WEBHOOK_SECRET` unset (and -> rely on a hard-to-guess `/github/webhook` URL plus IP allow-listing), or -> terminate signature verification at a reverse proxy in front of Twenty. - -## How auth resolution works - -`src/modules/github/connector/auth.ts` returns a token using the following -order: - -1. `GITHUB_TOKEN` (fine-grained PAT, `github_pat_…`) if present. Classic - PATs are rejected at startup. -2. Cached installation token, if still valid. -3. Fresh installation token minted from the GitHub App credentials. - -This makes the example easy to try in 30 seconds with a PAT, while still -demonstrating the production-grade GitHub App flow. - -## Tests - -The app ships with a small integration test suite that runs against a local -`twenty-app-dev-test` container. - -```bash -docker run -d --name twenty-app-dev-test \ - -p 2021:2021 twentycrm/twenty-app-dev:v2.0.3 - -cd packages/twenty-apps/community/github-connector -yarn test -``` - -The suite installs the app into the container, then asserts that every -object/field/logic-function is wired up and that webhook signature -verification behaves correctly. diff --git a/packages/twenty-apps/community/github-connector/package.json b/packages/twenty-apps/community/github-connector/package.json deleted file mode 100644 index 131fcfc984..0000000000 --- a/packages/twenty-apps/community/github-connector/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "github-connector", - "version": "0.2.0", - "license": "MIT", - "engines": { - "node": "^24.5.0", - "npm": "please-use-yarn", - "yarn": ">=4.0.2" - }, - "keywords": [ - "twenty-app" - ], - "packageManager": "yarn@4.13.0", - "scripts": { - "twenty": "twenty", - "lint": "oxlint -c .oxlintrc.json .", - "lint:fix": "oxlint --fix -c .oxlintrc.json .", - "test": "vitest run", - "test:watch": "vitest" - }, - "dependencies": { - "twenty-client-sdk": "2.13.0", - "twenty-sdk": "2.13.0" - }, - "devDependencies": { - "@types/node": "^24.7.2", - "@types/react": "^19.0.0", - "oxlint": "^0.16.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "typescript": "^5.9.3", - "vite-tsconfig-paths": "^4.2.1", - "vitest": "^4.0.0" - } -} diff --git a/packages/twenty-apps/community/github-connector/public/screenshots/app-listing.png b/packages/twenty-apps/community/github-connector/public/screenshots/app-listing.png deleted file mode 100644 index 1b3b88a238..0000000000 Binary files a/packages/twenty-apps/community/github-connector/public/screenshots/app-listing.png and /dev/null differ diff --git a/packages/twenty-apps/community/github-connector/public/screenshots/contributor-stats.png b/packages/twenty-apps/community/github-connector/public/screenshots/contributor-stats.png deleted file mode 100644 index 3b353e8bb4..0000000000 Binary files a/packages/twenty-apps/community/github-connector/public/screenshots/contributor-stats.png and /dev/null differ diff --git a/packages/twenty-apps/community/github-connector/public/screenshots/github-dashboard.png b/packages/twenty-apps/community/github-connector/public/screenshots/github-dashboard.png deleted file mode 100644 index f33c31d23a..0000000000 Binary files a/packages/twenty-apps/community/github-connector/public/screenshots/github-dashboard.png and /dev/null differ diff --git a/packages/twenty-apps/community/github-connector/public/screenshots/pull-requests-view.png b/packages/twenty-apps/community/github-connector/public/screenshots/pull-requests-view.png deleted file mode 100644 index c8ad0ccb9c..0000000000 Binary files a/packages/twenty-apps/community/github-connector/public/screenshots/pull-requests-view.png and /dev/null differ diff --git a/packages/twenty-apps/community/github-connector/src/__tests__/global-setup.ts b/packages/twenty-apps/community/github-connector/src/__tests__/global-setup.ts deleted file mode 100644 index c3f48da953..0000000000 --- a/packages/twenty-apps/community/github-connector/src/__tests__/global-setup.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import { appDevOnce, appUninstall } from 'twenty-sdk/cli'; - -const APP_PATH = process.cwd(); -const CONFIG_DIR = path.join(os.homedir(), '.twenty'); - -function writeConfig(apiUrl: string, apiKey: string) { - const payload = JSON.stringify( - { - remotes: { - local: { apiUrl, apiKey, accessToken: apiKey }, - }, - defaultRemote: 'local', - }, - null, - 2, - ); - - fs.mkdirSync(CONFIG_DIR, { recursive: true }); - fs.writeFileSync(path.join(CONFIG_DIR, 'config.test.json'), payload); -} - -export async function setup() { - const apiUrl = process.env.TWENTY_API_URL!; - const apiKey = process.env.TWENTY_API_KEY!; - - writeConfig(apiUrl, apiKey); - - await appUninstall({ appPath: APP_PATH }).catch(() => {}); - - const result = await appDevOnce({ - appPath: APP_PATH, - onProgress: (message: string) => console.log(`[dev] ${message}`), - }); - - if (!result.success) { - throw new Error( - `Dev sync failed: ${result.error?.message ?? 'Unknown error'}`, - ); - } -} - -export async function teardown() { - const uninstallResult = await appUninstall({ appPath: APP_PATH }); - - if (!uninstallResult.success) { - console.warn( - `App uninstall failed: ${uninstallResult.error?.message ?? 'Unknown error'}`, - ); - } -} diff --git a/packages/twenty-apps/community/github-connector/src/__tests__/helpers/metadata.ts b/packages/twenty-apps/community/github-connector/src/__tests__/helpers/metadata.ts deleted file mode 100644 index 8d17ff9668..0000000000 --- a/packages/twenty-apps/community/github-connector/src/__tests__/helpers/metadata.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { MetadataApiClient } from 'twenty-client-sdk/metadata'; - -const metadata = () => - new MetadataApiClient({ - headers: { - Authorization: `Bearer ${process.env.TWENTY_API_KEY}`, - }, - }); - -export async function findObjectByName(name: string) { - const client = metadata(); - const result = await client.query({ - objects: { - __args: { - filter: { isCustom: { is: true } }, - paging: { first: 50 }, - }, - edges: { - node: { - nameSingular: true, - fields: { - __args: { paging: { first: 500 } }, - edges: { node: { name: true, type: true } }, - }, - }, - }, - }, - }); - - return result.objects?.edges?.find((e) => e.node.nameSingular === name)?.node; -} - -type ExecutionResult = { - data: unknown; - status: string; - duration: number; - error: unknown; -}; - -export async function findLogicFunctionId( - universalIdentifier: string, -): Promise { - const client = metadata(); - const result = await client.query({ - findManyLogicFunctions: { - id: true, - universalIdentifier: true, - }, - }); - - const fn = result.findManyLogicFunctions?.find( - (f) => f.universalIdentifier === universalIdentifier, - ); - - if (!fn) { - throw new Error(`Logic function ${universalIdentifier} not found`); - } - - return fn.id; -} - -export async function executeLogicFunction( - id: string, - payload: Record, -): Promise { - const client = metadata(); - const result = await client.mutation({ - executeOneLogicFunction: { - __args: { input: { id, payload } }, - data: true, - status: true, - duration: true, - error: true, - }, - }); - - return result.executeOneLogicFunction as ExecutionResult; -} - -const BASE_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2021'; - -export async function callRoute( - path: string, - body: Record | string, - options: { - method?: string; - auth?: boolean; - headers?: Record; - } = {}, -): Promise<{ status: number; data: unknown }> { - const headers: Record = { - 'Content-Type': 'application/json', - ...(options.headers ?? {}), - }; - - if (options.auth) { - headers['Authorization'] = `Bearer ${process.env.TWENTY_API_KEY}`; - } - - const res = await fetch(`${BASE_URL}/s${path}`, { - method: options.method ?? 'POST', - headers, - body: typeof body === 'string' ? body : JSON.stringify(body), - }); - - let data: unknown = null; - try { - data = await res.json(); - } catch { - data = await res.text().catch(() => null); - } - return { status: res.status, data }; -} - -export async function findInstalledApp(universalIdentifier: string) { - const client = metadata(); - const result = await client.query({ - findManyApplications: { - id: true, - name: true, - universalIdentifier: true, - }, - }); - - return result.findManyApplications?.find( - (app) => app.universalIdentifier === universalIdentifier, - ); -} diff --git a/packages/twenty-apps/community/github-connector/src/__tests__/logic-functions.integration-test.ts b/packages/twenty-apps/community/github-connector/src/__tests__/logic-functions.integration-test.ts deleted file mode 100644 index 91f22016c0..0000000000 --- a/packages/twenty-apps/community/github-connector/src/__tests__/logic-functions.integration-test.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { beforeAll, describe, expect, it } from 'vitest'; - -import { - executeLogicFunction, - findLogicFunctionId, -} from './helpers/metadata'; - -const COUNT_PRS_FN_UI = '082227ae-2acc-4320-8d31-62ad6c443da6'; -const COUNT_ISSUES_FN_UI = 'd8cc32bf-6be9-44fc-920a-8bba510f045f'; -const COUNT_PROJECT_ITEMS_FN_UI = 'f7a3e1b2-5c4d-4e6f-8a9b-0d1c2e3f4a5b'; -const COUNT_CONTRIBUTORS_FN_UI = 'fe0a6f00-0d63-4cb9-9b3c-1d8186181830'; -const HANDLE_WEBHOOK_FN_UI = '22b199b3-2851-4a4f-99fd-4e79c188fe7d'; - -const fnIds: Record = {}; - -beforeAll(async () => { - fnIds.prs = await findLogicFunctionId(COUNT_PRS_FN_UI); - fnIds.issues = await findLogicFunctionId(COUNT_ISSUES_FN_UI); - fnIds.projectItems = await findLogicFunctionId(COUNT_PROJECT_ITEMS_FN_UI); - fnIds.contributors = await findLogicFunctionId(COUNT_CONTRIBUTORS_FN_UI); - fnIds.webhook = await findLogicFunctionId(HANDLE_WEBHOOK_FN_UI); -}); - -describe('logic functions are wired up', () => { - it('count-prs is reachable and returns the expected payload shape', async () => { - const result = await executeLogicFunction(fnIds.prs, { - body: { repos: ['fixture-org/fixture-repo'] }, - }); - expect(['SUCCESS', 'ERROR']).toContain(result.status); - if (result.status === 'SUCCESS') { - const data = result.data as { - totalPages: number; - repos: Array<{ owner: string; repo: string; pages: number }>; - }; - expect(typeof data.totalPages).toBe('number'); - expect(Array.isArray(data.repos)).toBe(true); - } - }); - - it('count-issues is reachable', async () => { - const result = await executeLogicFunction(fnIds.issues, { - body: { repos: ['fixture-org/fixture-repo'] }, - }); - expect(['SUCCESS', 'ERROR']).toContain(result.status); - }); - - it('count-project-items is reachable', async () => { - const result = await executeLogicFunction(fnIds.projectItems, { - body: { projects: [{ owner: 'fixture-org', number: 9999999 }] }, - }); - expect(['SUCCESS', 'ERROR']).toContain(result.status); - }); - - it('count-contributors iterates configured repos and returns the per-repo split', async () => { - const result = await executeLogicFunction(fnIds.contributors, { - body: { repos: ['fixture-org/fixture-repo'] }, - }); - expect(['SUCCESS', 'ERROR']).toContain(result.status); - if (result.status === 'SUCCESS') { - const data = result.data as { - totalPages: number; - repos: Array<{ owner: string; repo: string; pages: number }>; - }; - expect(typeof data.totalPages).toBe('number'); - expect(Array.isArray(data.repos)).toBe(true); - expect('orgMembers' in (data as Record)).toBe(false); - } - }); - - it('handle-github-webhook is registered (but rejects unsigned requests gracefully)', async () => { - expect(fnIds.webhook).toBeDefined(); - }); -}); diff --git a/packages/twenty-apps/community/github-connector/src/__tests__/schema.integration-test.ts b/packages/twenty-apps/community/github-connector/src/__tests__/schema.integration-test.ts deleted file mode 100644 index f2246f1ee7..0000000000 --- a/packages/twenty-apps/community/github-connector/src/__tests__/schema.integration-test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/modules/shared/universal-identifiers'; -import { describe, expect, it } from 'vitest'; - -import { findInstalledApp, findObjectByName } from './helpers/metadata'; - -describe('App installation', () => { - it('finds the installed GitHub Connector app', async () => { - const app = await findInstalledApp(APPLICATION_UNIVERSAL_IDENTIFIER); - expect(app).toBeDefined(); - expect(app?.name).toMatch(/github/i); - }); -}); - -describe('Contributor object', () => { - it('exists with the expected GitHub-only fields', async () => { - const obj = await findObjectByName('contributor'); - expect(obj).toBeDefined(); - - const names = obj!.fields.edges.map((e) => e.node.name); - expect(names).toContain('name'); - expect(names).toContain('ghLogin'); - expect(names).toContain('githubId'); - expect(names).toContain('avatarUrl'); - expect(names).toContain('contributions'); - - expect(names).not.toContain('isCoreTeam'); - expect(names).not.toContain('discordId'); - }); -}); - -describe('PullRequest object', () => { - it('exists with the expected fields and relations', async () => { - const obj = await findObjectByName('pullRequest'); - expect(obj).toBeDefined(); - - const names = obj!.fields.edges.map((e) => e.node.name); - expect(names).toContain('name'); - expect(names).toContain('githubNumber'); - expect(names).toContain('uniqueIdentifier'); - expect(names).toContain('url'); - expect(names).toContain('state'); - expect(names).toContain('mergedAt'); - expect(names).toContain('closedAt'); - expect(names).toContain('githubCreatedAt'); - expect(names).toContain('author'); - expect(names).toContain('merger'); - expect(names).toContain('reviews'); - expect(names).toContain('projectItems'); - }); -}); - -describe('PullRequestReviewEvent object', () => { - it('exists with the expected fields and relations', async () => { - const obj = await findObjectByName('pullRequestReviewEvent'); - expect(obj).toBeDefined(); - - const names = obj!.fields.edges.map((e) => e.node.name); - expect(names).toContain('title'); - expect(names).toContain('githubReviewId'); - expect(names).toContain('state'); - expect(names).toContain('submittedAt'); - expect(names).toContain('reviewer'); - expect(names).toContain('pullRequest'); - expect(names).toContain('review'); - }); -}); - -describe('PullRequestReview object', () => { - it('exists with the expected fields and relations', async () => { - const obj = await findObjectByName('pullRequestReview'); - expect(obj).toBeDefined(); - - const names = obj!.fields.edges.map((e) => e.node.name); - expect(names).toContain('title'); - expect(names).toContain('reviewKey'); - expect(names).toContain('state'); - expect(names).toContain('firstSubmittedAt'); - expect(names).toContain('lastSubmittedAt'); - expect(names).toContain('eventCount'); - expect(names).toContain('isSelfReview'); - expect(names).toContain('reviewer'); - expect(names).toContain('pullRequest'); - expect(names).toContain('reviewEvents'); - }); -}); - -describe('Issue object', () => { - it('exists with the expected fields and relations', async () => { - const obj = await findObjectByName('issue'); - expect(obj).toBeDefined(); - - const names = obj!.fields.edges.map((e) => e.node.name); - expect(names).toContain('title'); - expect(names).toContain('githubNumber'); - expect(names).toContain('uniqueIdentifier'); - expect(names).toContain('githubUrl'); - expect(names).toContain('state'); - expect(names).toContain('labels'); - expect(names).toContain('githubCreatedAt'); - expect(names).toContain('closedAt'); - expect(names).toContain('repo'); - expect(names).toContain('author'); - expect(names).toContain('projectItems'); - }); -}); - -describe('ProjectItem object', () => { - it('exists with the expected fields and relations', async () => { - const obj = await findObjectByName('projectItem'); - expect(obj).toBeDefined(); - - const names = obj!.fields.edges.map((e) => e.node.name); - expect(names).toContain('name'); - expect(names).toContain('githubProjectItemId'); - expect(names).toContain('status'); - expect(names).toContain('sprint'); - expect(names).toContain('assignees'); - expect(names).toContain('priority'); - expect(names).toContain('mainAssignee'); - expect(names).toContain('linkedIssue'); - expect(names).toContain('linkedPullRequest'); - expect(names).toContain('githubUrl'); - expect(names).toContain('repo'); - }); -}); diff --git a/packages/twenty-apps/community/github-connector/src/__tests__/webhook-signature.integration-test.ts b/packages/twenty-apps/community/github-connector/src/__tests__/webhook-signature.integration-test.ts deleted file mode 100644 index 714786048b..0000000000 --- a/packages/twenty-apps/community/github-connector/src/__tests__/webhook-signature.integration-test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { createHmac } from 'crypto'; - -import { - getRawBodyForSignature, - verifyGitHubSignature, -} from 'src/modules/github/connector/webhook-signature'; - -const SECRET = 'super-secret-shared-string'; - -function sign(body: string): string { - return `sha256=${createHmac('sha256', SECRET).update(body).digest('hex')}`; -} - -describe('verifyGitHubSignature', () => { - it('accepts a valid signature', () => { - const body = '{"action":"opened","number":42}'; - const result = verifyGitHubSignature({ - rawBody: body, - signatureHeader: sign(body), - secret: SECRET, - }); - expect(result.ok).toBe(true); - }); - - it('rejects a tampered body', () => { - const body = '{"action":"opened","number":42}'; - const signature = sign(body); - const result = verifyGitHubSignature({ - rawBody: body.replace('42', '43'), - signatureHeader: signature, - secret: SECRET, - }); - expect(result.ok).toBe(false); - }); - - it('rejects a wrong secret', () => { - const body = '{"action":"opened","number":42}'; - const result = verifyGitHubSignature({ - rawBody: body, - signatureHeader: sign(body), - secret: 'other-secret', - }); - expect(result.ok).toBe(false); - }); - - it('rejects a missing header', () => { - const result = verifyGitHubSignature({ - rawBody: 'anything', - signatureHeader: undefined, - secret: SECRET, - }); - expect(result).toMatchObject({ - ok: false, - reason: 'missing X-Hub-Signature-256 header', - }); - }); - - it('rejects a header without sha256= prefix', () => { - const result = verifyGitHubSignature({ - rawBody: 'anything', - signatureHeader: 'sha1=deadbeef', - secret: SECRET, - }); - expect(result).toMatchObject({ - ok: false, - reason: 'malformed signature header', - }); - }); - - it('rejects when length differs', () => { - const result = verifyGitHubSignature({ - rawBody: 'anything', - signatureHeader: 'sha256=tooshort', - secret: SECRET, - }); - expect(result).toMatchObject({ - ok: false, - reason: 'signature length mismatch', - }); - }); -}); - -describe('getRawBodyForSignature', () => { - it('prefers event.rawBody when the runtime forwarded it', () => { - const original = '{ "action": "opened", "number": 42 }'; - expect( - getRawBodyForSignature({ - body: { action: 'opened', number: 42 }, - rawBody: original, - }), - ).toBe(original); - }); - - it('falls back to string body when rawBody is not provided', () => { - expect( - getRawBodyForSignature({ body: '{"a":1}', isBase64Encoded: false }), - ).toBe('{"a":1}'); - }); - - it('decodes base64 bodies', () => { - const original = '{"a":1}'; - const b64 = Buffer.from(original, 'utf8').toString('base64'); - expect(getRawBodyForSignature({ body: b64, isBase64Encoded: true })).toBe( - original, - ); - }); - - it('returns null for parsed object bodies (raw bytes lost)', () => { - expect(getRawBodyForSignature({ body: { a: 1 } })).toBeNull(); - }); - - it('returns empty string for null/undefined', () => { - expect(getRawBodyForSignature({ body: null })).toBe(''); - }); -}); - -describe('verifyGitHubSignature with parsed body', () => { - it('rejects with a clear reason when the runtime parsed the JSON', () => { - const result = verifyGitHubSignature({ - rawBody: null, - signatureHeader: 'sha256=deadbeef', - secret: SECRET, - }); - expect(result).toMatchObject({ - ok: false, - reason: - 'raw request body is unavailable (the runtime parsed it as JSON); HMAC cannot be verified', - }); - }); -}); - -describe('end-to-end: server forwards rawBody, signature verifies', () => { - it('verifies a signature when rawBody is present alongside parsed body', () => { - const original = '{ "action": "opened", "number": 42 }'; - const event = { - body: { action: 'opened', number: 42 }, - rawBody: original, - isBase64Encoded: false, - }; - - const rawBody = getRawBodyForSignature(event); - const result = verifyGitHubSignature({ - rawBody, - signatureHeader: sign(original), - secret: SECRET, - }); - expect(result.ok).toBe(true); - }); -}); diff --git a/packages/twenty-apps/community/github-connector/src/application-config.ts b/packages/twenty-apps/community/github-connector/src/application-config.ts deleted file mode 100644 index 673c442388..0000000000 --- a/packages/twenty-apps/community/github-connector/src/application-config.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { defineApplication } from 'twenty-sdk/define'; - -import { - APP_ABOUT_DESCRIPTION, - APP_DESCRIPTION, - APP_DISPLAY_NAME, - APPLICATION_UNIVERSAL_IDENTIFIER, - DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -} from 'src/modules/shared/universal-identifiers'; - -export default defineApplication({ - universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, - displayName: APP_DISPLAY_NAME, - description: APP_DESCRIPTION, - aboutDescription: APP_ABOUT_DESCRIPTION, - icon: 'IconBrandGithub', - defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - screenshots: [ - 'public/screenshots/app-listing.png', - 'public/screenshots/github-dashboard.png', - 'public/screenshots/pull-requests-view.png', - 'public/screenshots/contributor-stats.png', - ], - applicationVariables: { - GITHUB_TOKEN: { - universalIdentifier: 'fb1d2e91-3a75-4c89-9d6b-1e2f7a4c5d8e', - description: - 'Fine-grained Personal Access Token (github_pat_…) from https://github.com/settings/personal-access-tokens — needs Read-only access to Contents, Issues, Pull requests and Metadata (and Organization → Projects for Projects v2). Classic PATs are not supported. When set, takes precedence over the GitHub App credentials below.', - isSecret: true, - }, - GITHUB_APP_ID: { - universalIdentifier: '2a196d91-4c1b-4f8d-bc34-6693fcdaa771', - description: - 'GitHub App ID. Used together with GITHUB_APP_PRIVATE_KEY and GITHUB_APP_INSTALLATION_ID when GITHUB_TOKEN is unset.', - isSecret: false, - }, - GITHUB_APP_PRIVATE_KEY: { - universalIdentifier: 'c5591dd4-f653-4b92-bec9-970ddc8e10cc', - description: 'GitHub App PEM private key (BEGIN/END PRIVATE KEY block).', - isSecret: true, - }, - GITHUB_APP_INSTALLATION_ID: { - universalIdentifier: '0c39a59a-ee2e-49a9-88c7-3fbe6cb04ad0', - description: 'GitHub App installation ID for your org.', - isSecret: false, - }, - GITHUB_WEBHOOK_SECRET: { - universalIdentifier: 'b9f3c2d8-1e7a-4d56-9c8b-3a2f1e5d7c9a', - description: - 'Shared secret used to verify the X-Hub-Signature-256 HMAC on incoming GitHub webhooks. When unset, signature verification is skipped (use only in dev/test).', - isSecret: true, - }, - GITHUB_REPOS: { - universalIdentifier: '7d1e9c84-2f63-4a58-9b0d-5e8a3c1f7b29', - description: - 'Comma-separated list of `owner/repo` to sync (e.g. `twentyhq/twenty,octo/hello`). Used by the manual fetch routes.', - isSecret: false, - }, - GITHUB_PROJECTS: { - universalIdentifier: 'e3a8c7d2-4b95-4e1f-8a6c-9d2b5f7e1c84', - description: - 'Comma-separated list of GitHub Projects (v2) to sync. Each entry is `owner/number` (e.g. `twentyhq/24,octo/3`). Owner can be an organization or a user. Full URLs like `https://github.com/orgs/twentyhq/projects/24` or `https://github.com/users/octo/projects/3` are also accepted.', - isSecret: false, - }, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/default-role.ts b/packages/twenty-apps/community/github-connector/src/default-role.ts deleted file mode 100644 index 8d3e930cc1..0000000000 --- a/packages/twenty-apps/community/github-connector/src/default-role.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { defineRole } from 'twenty-sdk/define'; - -import { - APP_DISPLAY_NAME, - DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, -} from 'src/modules/shared/universal-identifiers'; - -export default defineRole({ - universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, - label: `${APP_DISPLAY_NAME} default function role`, - description: `${APP_DISPLAY_NAME} default function role`, - canReadAllObjectRecords: true, - canUpdateAllObjectRecords: true, - canSoftDeleteAllObjectRecords: true, - canDestroyAllObjectRecords: false, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/auth.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/auth.ts deleted file mode 100644 index 5cb01a98ba..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/auth.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { createSign } from 'crypto'; - -export function normalizePem(raw: string): string { - let pem = raw.replace(/\\n/g, '\n').trim(); - - if (!pem.includes('\n')) { - const beginMatch = pem.match(/^(-----BEGIN [A-Z ]+-----)/); - const endMatch = pem.match(/(-----END [A-Z ]+-----)$/); - - if (beginMatch && endMatch) { - const header = beginMatch[1]; - const footer = endMatch[1]; - const body = pem.slice(header.length, pem.length - footer.length); - const lines = body.match(/.{1,64}/g) ?? []; - pem = [header, ...lines, footer].join('\n'); - } - } - - return pem; -} - -function base64url(input: Buffer | string): string { - const buf = typeof input === 'string' ? Buffer.from(input) : input; - return buf.toString('base64url'); -} - -function signJwt(appId: string, privateKey: string): string { - const now = Math.floor(Date.now() / 1000); - const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' })); - const payload = base64url( - JSON.stringify({ iat: now - 60, exp: now + 600, iss: appId }), - ); - - const signature = createSign('RSA-SHA256') - .update(`${header}.${payload}`) - .sign(privateKey, 'base64url'); - - return `${header}.${payload}.${signature}`; -} - -type CachedToken = { token: string; expiresAt: number }; -let cached: CachedToken | null = null; - -const TOKEN_MARGIN_MS = 5 * 60 * 1000; - -async function requestInstallationToken( - appId: string, - privateKey: string, - installationId: string, -): Promise { - const jwt = signJwt(appId, privateKey); - - const res = await fetch( - `https://api.github.com/app/installations/${installationId}/access_tokens`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${jwt}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - }, - ); - - if (!res.ok) { - const body = await res.text(); - throw new Error( - `GitHub App token exchange failed (${res.status}): ${body}`, - ); - } - - const data = (await res.json()) as { - token: string; - expires_at: string; - }; - - return { - token: data.token, - expiresAt: new Date(data.expires_at).getTime(), - }; -} - -function assertFineGrainedPat(token: string): void { - if (token.startsWith('github_pat_')) return; - throw new Error( - 'GITHUB_TOKEN must be a fine-grained Personal Access Token (starts with `github_pat_`). Classic PATs are not supported — create one at https://github.com/settings/personal-access-tokens.', - ); -} - -export async function getGitHubToken(): Promise { - const pat = process.env.GITHUB_TOKEN?.trim(); - if (pat) { - assertFineGrainedPat(pat); - return pat; - } - - if (cached && Date.now() < cached.expiresAt - TOKEN_MARGIN_MS) { - return cached.token; - } - - const appId = process.env.GITHUB_APP_ID; - const rawKey = process.env.GITHUB_APP_PRIVATE_KEY; - const installationId = process.env.GITHUB_APP_INSTALLATION_ID; - - if (!appId || !rawKey || !installationId) { - const missing = [ - !appId && 'GITHUB_APP_ID', - !rawKey && 'GITHUB_APP_PRIVATE_KEY', - !installationId && 'GITHUB_APP_INSTALLATION_ID', - ] - .filter(Boolean) - .join(', '); - throw new Error( - `Missing GitHub credentials. Set GITHUB_TOKEN (fine-grained PAT) or all of: ${missing}.`, - ); - } - - const privateKey = normalizePem(rawKey); - cached = await requestInstallationToken(appId, privateKey, installationId); - - return cached.token; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/config.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/config.ts deleted file mode 100644 index c74eb46d31..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/config.ts +++ /dev/null @@ -1,55 +0,0 @@ -export type GithubRepo = { owner: string; repo: string }; - -export function parseGithubRepo(entry: string): GithubRepo | null { - const trimmed = entry.trim(); - if (!trimmed) return null; - const match = trimmed.match(/^([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)$/); - if (!match) return null; - return { owner: match[1], repo: match[2] }; -} - -export function getGithubRepos(): string[] { - const raw = process.env.GITHUB_REPOS ?? ''; - return raw - .split(',') - .map(parseGithubRepo) - .filter((r): r is GithubRepo => r !== null) - .map((r) => `${r.owner}/${r.repo}`); -} - -export type GithubProject = { owner: string; number: number }; - -export function parseGithubProject(entry: string): GithubProject | null { - const trimmed = entry.trim(); - if (!trimmed) return null; - - const urlMatch = trimmed.match( - /github\.com\/(?:orgs|users)\/([^/]+)\/projects\/(\d+)/i, - ); - if (urlMatch) { - const owner = urlMatch[1]; - const number = Number.parseInt(urlMatch[2], 10); - if (owner && Number.isFinite(number) && number > 0) { - return { owner, number }; - } - } - - const shortMatch = trimmed.match(/^([^/\s]+)\/(\d+)$/); - if (shortMatch) { - const owner = shortMatch[1]; - const number = Number.parseInt(shortMatch[2], 10); - if (owner && Number.isFinite(number) && number > 0) { - return { owner, number }; - } - } - - return null; -} - -export function getGithubProjects(): GithubProject[] { - const raw = process.env.GITHUB_PROJECTS ?? ''; - return raw - .split(',') - .map(parseGithubProject) - .filter((p): p is GithubProject => p !== null); -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/count-across-repos.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/count-across-repos.ts deleted file mode 100644 index 414218a25e..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/count-across-repos.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - getGithubRepos, - parseGithubRepo, -} from 'src/modules/github/connector/config'; - -export type RepoCount = { - owner: string; - repo: string; - totalCount: number; - pages: number; -}; - -export type CountAcrossReposResult = { - totalPages: number; - repos: RepoCount[]; -}; - -const PAGE_SIZE = 100; - -export async function countAcrossRepos( - bodyRepos: string[] | undefined, - count: (owner: string, repo: string) => Promise, - logTag: string, -): Promise { - const repos = - bodyRepos && bodyRepos.length > 0 ? bodyRepos : getGithubRepos(); - - const results: RepoCount[] = []; - let totalPages = 0; - - for (const fullRepo of repos) { - const parsed = parseGithubRepo(fullRepo); - if (!parsed) { - console.warn(`[${logTag}] Skipping malformed repo entry: ${fullRepo}`); - continue; - } - const { owner, repo } = parsed; - const totalCount = await count(owner, repo); - const pages = Math.max(Math.ceil(totalCount / PAGE_SIZE), 1); - results.push({ owner, repo, totalCount, pages }); - totalPages += pages; - } - - return { totalPages, repos: results }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/github-client.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/github-client.ts deleted file mode 100644 index 46051e6d71..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/github-client.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { getGitHubToken } from 'src/modules/github/connector/auth'; - -const GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql'; -const LOW_REMAINING_THRESHOLD = 200; - -export class GitHubRateLimitError extends Error { - constructor( - message: string, - public readonly kind: 'primary' | 'secondary', - public readonly remaining: number | null, - public readonly limit: number | null, - public readonly resetAt: Date | null, - public readonly retryAfterSeconds: number | null, - ) { - super(message); - this.name = 'GitHubRateLimitError'; - } -} - -function readRateLimitHeaders(res: Response) { - const num = (h: string) => { - const v = res.headers.get(h); - return v === null ? null : Number(v); - }; - const reset = num('x-ratelimit-reset'); - return { - limit: num('x-ratelimit-limit'), - remaining: num('x-ratelimit-remaining'), - used: num('x-ratelimit-used'), - resource: res.headers.get('x-ratelimit-resource'), - resetAt: - reset === null || Number.isNaN(reset) ? null : new Date(reset * 1000), - retryAfter: num('retry-after'), - }; -} - -export async function githubGraphql( - query: string, - variables: Record, -): Promise { - const queryName = query.match(/query\s*\(/) - ? query.slice(0, 60).replace(/\s+/g, ' ').trim() - : 'mutation'; - console.log( - `[github-gql] Executing: ${queryName} vars=${JSON.stringify(Object.keys(variables))}`, - ); - const token = await getGitHubToken(); - const res = await fetch(GITHUB_GRAPHQL_URL, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query, variables }), - }); - - const rl = readRateLimitHeaders(res); - if (rl.remaining !== null && rl.limit !== null) { - const resetIn = rl.resetAt - ? Math.max(0, Math.round((rl.resetAt.getTime() - Date.now()) / 1000)) - : null; - const isLow = - rl.remaining <= LOW_REMAINING_THRESHOLD || - rl.remaining / rl.limit < 0.1; - const tag = isLow - ? '[github-gql][rate-limit-low]' - : '[github-gql][rate-limit]'; - console.log( - `${tag} resource=${rl.resource ?? 'graphql'} remaining=${rl.remaining}/${rl.limit} used=${rl.used ?? '?'} resetIn=${resetIn ?? '?'}s`, - ); - } - - if (!res.ok) { - const body = await res.text(); - const lower = body.toLowerCase(); - const isSecondary = - res.status === 403 && - (lower.includes('secondary rate limit') || - lower.includes('abuse detection')); - const isPrimary = - (res.status === 403 || res.status === 429) && - lower.includes('rate limit') && - !isSecondary; - - if (isPrimary || isSecondary) { - throw new GitHubRateLimitError( - `GitHub ${isSecondary ? 'secondary' : 'primary'} rate limit hit (${res.status}): ${body.slice(0, 200)}`, - isSecondary ? 'secondary' : 'primary', - rl.remaining, - rl.limit, - rl.resetAt, - rl.retryAfter, - ); - } - - throw new Error( - `GitHub GraphQL ${res.status} ${res.statusText}: ${body.slice(0, 500)}`, - ); - } - - const json = (await res.json()) as { - data: T; - errors?: Array<{ type?: string; message: string }>; - }; - - if (json.errors?.length) { - const isRateLimited = json.errors.some( - (e) => e.type === 'RATE_LIMITED' || /rate limit/i.test(e.message), - ); - if (isRateLimited) { - throw new GitHubRateLimitError( - `GitHub GraphQL RATE_LIMITED: ${json.errors.map((e) => e.message).join(', ')}`, - 'primary', - rl.remaining, - rl.limit, - rl.resetAt, - rl.retryAfter, - ); - } - throw new Error( - `GraphQL errors: ${json.errors.map((e) => e.message).join(', ')}`, - ); - } - - return json.data; -} - -export async function githubGraphqlOptional( - query: string, - variables: Record, -): Promise { - try { - return await githubGraphql(query, variables); - } catch (err) { - const msg = err instanceof Error ? err.message : ''; - if (/Could not resolve to a (User|Organization)/i.test(msg)) return null; - throw err; - } -} - -export type GithubPage = { - totalCount: number; - pageInfo: { hasNextPage: boolean; endCursor: string | null }; - nodes: T[]; -}; - -export type Paged> = TItems & { - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -export const EMPTY_PAGE = { - totalCount: 0, - hasMore: false, - endCursor: null, -} as const; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/github-user.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/github-user.ts deleted file mode 100644 index 297596ed90..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/github-user.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type GitHubUser = { - login: string; - id: number; - avatar_url?: string | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/webhook-payload.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/webhook-payload.ts deleted file mode 100644 index c71c26c5b9..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/webhook-payload.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { GitHubUser } from 'src/modules/github/connector/github-user'; -import type { GitHubPullRequest } from 'src/modules/github/pull-request/types/github-pull-request'; -import type { GitHubReview } from 'src/modules/github/pull-request-review-event/types/github-review'; -import type { GitHubIssue } from 'src/modules/github/issue/types/github-issue'; -import type { GitHubProjectV2Item } from 'src/modules/github/project-item/types/github-project-v2-item'; - -export type GitHubWebhookPayload = { - action: string; - pull_request?: GitHubPullRequest; - review?: GitHubReview; - issue?: GitHubIssue; - projects_v2_item?: GitHubProjectV2Item; - sender: GitHubUser; - repository?: { full_name: string }; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/connector/webhook-signature.ts b/packages/twenty-apps/community/github-connector/src/modules/github/connector/webhook-signature.ts deleted file mode 100644 index 51b4fd5241..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/connector/webhook-signature.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { createHmac, timingSafeEqual } from 'crypto'; - -export type SignatureVerificationResult = - | { ok: true } - | { ok: false; reason: string }; - -export function getRawBodyForSignature(event: { - body: unknown; - isBase64Encoded?: boolean; - rawBody?: string; -}): string | null { - if (typeof event.rawBody === 'string') { - return event.rawBody; - } - const raw = event.body; - if (raw == null) return ''; - if (typeof raw === 'string') { - return event.isBase64Encoded - ? Buffer.from(raw, 'base64').toString('utf8') - : raw; - } - return null; -} - -export function verifyGitHubSignature({ - rawBody, - signatureHeader, - secret, -}: { - rawBody: string | null; - signatureHeader: string | undefined; - secret: string; -}): SignatureVerificationResult { - if (rawBody === null) { - return { - ok: false, - reason: - 'raw request body is unavailable (the runtime parsed it as JSON); HMAC cannot be verified', - }; - } - if (!signatureHeader) { - return { ok: false, reason: 'missing X-Hub-Signature-256 header' }; - } - const prefix = 'sha256='; - if (!signatureHeader.startsWith(prefix)) { - return { ok: false, reason: 'malformed signature header' }; - } - const provided = signatureHeader.slice(prefix.length); - const expected = createHmac('sha256', secret).update(rawBody).digest('hex'); - if (provided.length !== expected.length) { - return { ok: false, reason: 'signature length mismatch' }; - } - const ok = timingSafeEqual( - Buffer.from(provided, 'utf8'), - Buffer.from(expected, 'utf8'), - ); - return ok ? { ok: true } : { ok: false, reason: 'signature mismatch' }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/command-menu-items/contributor-stats.command-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/command-menu-items/contributor-stats.command-menu-item.ts deleted file mode 100644 index ebc49ffdc2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/command-menu-items/contributor-stats.command-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineCommandMenuItem, objectMetadataItem } from 'twenty-sdk/define'; - -export default defineCommandMenuItem({ - universalIdentifier: '7d3f2b9e-5c0a-4e8b-ad6e-2f9c3b4d5e6a', - frontComponentUniversalIdentifier: '6c2f1a8d-4b9e-4f7a-9c5d-1e8b2a3d4c5f', - label: 'Contributor Stats', - icon: 'IconChartBar', - isPinned: false, - conditionalAvailabilityExpression: - objectMetadataItem.nameSingular === 'contributor', -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/command-menu-items/fetch-contributors.command-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/command-menu-items/fetch-contributors.command-menu-item.ts deleted file mode 100644 index 72d5a7a30a..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/command-menu-items/fetch-contributors.command-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineCommandMenuItem, objectMetadataItem } from 'twenty-sdk/define'; - -export default defineCommandMenuItem({ - universalIdentifier: '4640992f-c2c9-4bba-b5df-9c8f05dc9e80', - frontComponentUniversalIdentifier: '08f40f82-24ed-4f3e-8c99-695151e90e38', - label: 'Fetch Contributors', - icon: 'IconUsers', - isPinned: false, - conditionalAvailabilityExpression: - objectMetadataItem.nameSingular === 'contributor', -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/components/leaderboard.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/components/leaderboard.tsx deleted file mode 100644 index 688f7981e9..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/components/leaderboard.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { type ReactNode } from 'react'; - -import { THEME } from 'src/modules/github/contributor/components/theme'; - -export type LeaderboardEntry = { - id: string; - name: string | null; - ghLogin: string | null; - avatarUrl: string | null; - count: number; -}; - -const ROW_HEIGHT = 32; -const CELL_PADDING_X = 8; -const RANK_COL_WIDTH = 36; -const COUNT_COL_WIDTH = 72; - -const styles = { - root: { - display: 'flex', - flexDirection: 'column', - width: '100%', - height: '100%', - minHeight: 0, - fontFamily: THEME.fontFamily, - color: THEME.fontPrimary, - background: THEME.bgPrimary, - boxSizing: 'border-box', - overflow: 'hidden', - } as const, - table: { - display: 'flex', - flexDirection: 'column', - flex: 1, - minHeight: 0, - overflowY: 'auto', - overflowX: 'hidden', - width: '100%', - } as const, - headerRow: { - display: 'flex', - alignItems: 'stretch', - height: ROW_HEIGHT, - flexShrink: 0, - borderBottom: `1px solid ${THEME.borderLight}`, - background: THEME.bgPrimary, - } as const, - headerCell: { - display: 'flex', - alignItems: 'center', - height: '100%', - padding: `0 ${CELL_PADDING_X}px`, - borderRight: `1px solid ${THEME.borderLight}`, - color: THEME.fontTertiary, - fontSize: 12, - fontWeight: 500, - boxSizing: 'border-box', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - userSelect: 'none', - } as const, - headerCellLast: { - borderRight: 'none', - } as const, - row: { - display: 'flex', - alignItems: 'stretch', - height: ROW_HEIGHT, - flexShrink: 0, - width: '100%', - background: THEME.bgPrimary, - color: THEME.fontPrimary, - textDecoration: 'none', - boxSizing: 'border-box', - } as const, - cell: { - display: 'flex', - alignItems: 'center', - height: '100%', - padding: `0 ${CELL_PADDING_X}px`, - borderBottom: `1px solid ${THEME.borderLight}`, - borderRight: `1px solid ${THEME.borderLight}`, - fontSize: 13, - boxSizing: 'border-box', - minWidth: 0, - overflow: 'hidden', - transition: 'background-color 80ms ease', - } as const, - cellLast: { - borderRight: 'none', - } as const, - rankCell: { - width: RANK_COL_WIDTH, - flexShrink: 0, - justifyContent: 'flex-end', - color: THEME.fontTertiary, - fontSize: 12, - fontVariantNumeric: 'tabular-nums', - } as const, - contributorCell: { - flex: 1, - minWidth: 0, - gap: 6, - } as const, - countCell: { - width: COUNT_COL_WIDTH, - flexShrink: 0, - justifyContent: 'flex-end', - color: THEME.fontSecondary, - fontVariantNumeric: 'tabular-nums', - fontWeight: 500, - } as const, - avatar: { - width: 16, - height: 16, - borderRadius: '50%', - objectFit: 'cover', - background: THEME.bgTertiary, - flexShrink: 0, - } as const, - nameWrapper: { - display: 'flex', - alignItems: 'baseline', - gap: 6, - minWidth: 0, - flex: 1, - lineHeight: 1.2, - } as const, - name: { - fontSize: 13, - fontWeight: 500, - color: THEME.fontPrimary, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - } as const, - login: { - fontSize: 12, - fontWeight: 400, - color: THEME.fontTertiary, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - } as const, - empty: { - flex: 1, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - color: THEME.fontTertiary, - fontSize: 13, - padding: 16, - textAlign: 'center', - } as const, -}; - -type LeaderboardProps = { - contributorColumnLabel: string; - countColumnLabel: string; - entries: LeaderboardEntry[]; - loading: boolean; - error: string | null; - emptyMessage?: string; -}; - -const renderState = (content: ReactNode) => ( -
{content}
-); - -const Row = ({ - rank, - entry, - countLabel, -}: { - rank: number; - entry: LeaderboardEntry; - countLabel: string; -}) => { - const label = entry.name ?? entry.ghLogin ?? 'Unknown'; - const profileHref = entry.ghLogin - ? `https://github.com/${entry.ghLogin}` - : undefined; - - const onEnter = (e: React.MouseEvent) => { - e.currentTarget.style.backgroundColor = THEME.bgSecondary; - }; - const onLeave = (e: React.MouseEvent) => { - e.currentTarget.style.backgroundColor = THEME.bgPrimary; - }; - - const cells = ( - <> -
{rank}
-
- {entry.avatarUrl ? ( - - ) : ( -
- )} -
- {label} - {entry.ghLogin && entry.ghLogin !== label && ( - @{entry.ghLogin} - )} -
-
-
- {entry.count} -
- - ); - - if (profileHref) { - return ( - - {cells} - - ); - } - - return ( -
- {cells} -
- ); -}; - -export const Leaderboard = ({ - contributorColumnLabel, - countColumnLabel, - entries, - loading, - error, - emptyMessage = 'No activity in the last 90 days', -}: LeaderboardProps) => ( -
-
-
#
-
- {contributorColumnLabel} -
-
- {countColumnLabel} -
-
- {error ? ( - renderState(error) - ) : loading && entries.length === 0 ? ( - renderState('Loading...') - ) : entries.length === 0 ? ( - renderState(emptyMessage) - ) : ( -
- {entries.map((entry, i) => ( - - ))} -
- )} -
-); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/components/theme.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/components/theme.ts deleted file mode 100644 index d55b5f74f4..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/components/theme.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const THEME = { - fontFamily: - 'var(--t-font-family, Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif)', - borderRadius: 'var(--t-border-radius-sm, 4px)', - - fontPrimary: 'var(--t-font-color-primary)', - fontSecondary: 'var(--t-font-color-secondary)', - fontTertiary: 'var(--t-font-color-tertiary)', - fontLight: 'var(--t-font-color-light)', - - borderLight: 'var(--t-border-color-light)', - borderMedium: 'var(--t-border-color-medium)', - - bgPrimary: 'var(--t-background-primary)', - bgSecondary: 'var(--t-background-secondary)', - bgTertiary: 'var(--t-background-tertiary)', - bgTransparentLight: 'var(--t-background-transparent-light)', - bgTransparentLighter: 'var(--t-background-transparent-lighter)', - - chartAuthored: 'var(--t-color-purple)', - chartMerged: 'var(--t-color-green)', - chartReviewed: 'var(--t-color-blue)', -} as const; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/assigned-project-items-on-contributor.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/assigned-project-items-on-contributor.field.ts deleted file mode 100644 index 0199c89905..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/assigned-project-items-on-contributor.field.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { MAIN_ASSIGNEE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/fields/main-assignee-on-project-item.field'; - -export const ASSIGNED_PROJECT_ITEMS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER = - 'b35aed82-2c01-4f37-b3f9-cf67dc269d2c'; - -export default defineField({ - universalIdentifier: - ASSIGNED_PROJECT_ITEMS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'assignedProjectItems', - label: 'Assigned Items', - icon: 'IconLayoutKanban', - relationTargetObjectMetadataUniversalIdentifier: - PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - MAIN_ASSIGNEE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/authored-issues-on-contributor.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/authored-issues-on-contributor.field.ts deleted file mode 100644 index 5d6b2c1375..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/authored-issues-on-contributor.field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { ISSUE_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/objects/issue.object'; -import { ISSUE_AUTHOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/fields/author-on-issue.field'; - -export const AUTHORED_ISSUES_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER = - '31f185c0-d2a2-47e2-9132-8cbac05f174a'; - -export default defineField({ - universalIdentifier: AUTHORED_ISSUES_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'authoredIssues', - label: 'Authored Issues', - icon: 'IconBug', - relationTargetObjectMetadataUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - ISSUE_AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/authored-prs-on-contributor.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/authored-prs-on-contributor.field.ts deleted file mode 100644 index 4f72b653b9..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/authored-prs-on-contributor.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { AUTHOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/author-on-pull-request.field'; - -export const AUTHORED_PRS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER = - 'c3d4e5f6-2b3c-4d4e-9f6a-7b8c9d0e1f2a'; - -export default defineField({ - universalIdentifier: AUTHORED_PRS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'authoredPrs', - label: 'Authored PRs', - icon: 'IconGitPullRequest', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/merged-pull-requests-on-contributor.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/merged-pull-requests-on-contributor.field.ts deleted file mode 100644 index 9dde443d05..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/merged-pull-requests-on-contributor.field.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { MERGER_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/merger-on-pull-request.field'; - -export const MERGED_PULL_REQUESTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER = - '02ec0165-fb5a-46f9-b7c7-a0fd605befa2'; - -export default defineField({ - universalIdentifier: - MERGED_PULL_REQUESTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'mergedPullRequests', - label: 'Merged Pull Requests', - icon: 'IconGitMerge', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - MERGER_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/review-events-on-contributor.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/review-events-on-contributor.field.ts deleted file mode 100644 index 2081d4d0d2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/review-events-on-contributor.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { REVIEWER_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/fields/reviewer-on-review-event.field'; - -export const REVIEW_EVENTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER = - 'e4f5a6b7-8c9d-4e0f-a1b2-c3d4e5f6a7b8'; - -export default defineField({ - universalIdentifier: REVIEW_EVENTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviewEvents', - label: 'Review Events', - icon: 'IconEye', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEWER_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/reviews-on-contributor.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/reviews-on-contributor.field.ts deleted file mode 100644 index 3d99635a41..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/fields/reviews-on-contributor.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { REVIEWER_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/fields/reviewer-on-review.field'; - -export const REVIEWS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER = - '564e7f97-74dc-4e9b-a4bc-ed4215ab9ac7'; - -export default defineField({ - universalIdentifier: REVIEWS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviews', - label: 'Reviews', - icon: 'IconEye', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEWER_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/contributor-stats.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/contributor-stats.front-component.tsx deleted file mode 100644 index e2aee6d49d..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/contributor-stats.front-component.tsx +++ /dev/null @@ -1,681 +0,0 @@ -import { useEffect, useMemo, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { enqueueSnackbar, useRecordId } from 'twenty-sdk/front-component'; - -import { THEME } from 'src/modules/github/contributor/components/theme'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; - -type Period = 'week' | 'month' | '3months' | 'year'; - -type Bucket = { - key: string; - label: string; - start: string; - end: string; - prAuthored: number; - prMerged: number; - prReviewed: number; -}; - -type ContributorInfo = { - id: string; - name: string | null; - ghLogin: string | null; - avatarUrl: string | null; -}; - -type StatsResponse = { - contributor: ContributorInfo; - period: Period; - granularity: 'day' | 'week' | 'month'; - buckets: Bucket[]; - totals: { prAuthored: number; prMerged: number; prReviewed: number }; - truncated: { - prAuthored: boolean; - prMerged: boolean; - prReviewed: boolean; - }; - error?: string; -}; - -type SearchResponse = { - contributors: ContributorInfo[]; -}; - -const readSerializedValue = ( - e: React.SyntheticEvent, -): string | undefined => { - const obj = e as { detail?: { value?: string }; value?: string }; - if (typeof obj.detail?.value === 'string') return obj.detail.value; - if (typeof obj.value === 'string') return obj.value; - return undefined; -}; - -const onValueChange = - (fn: (value: string) => void) => - (e: React.SyntheticEvent) => { - const v = readSerializedValue(e); - if (typeof v === 'string') fn(v); - }; - -const PERIOD_OPTIONS: Array<{ value: Period; label: string }> = [ - { value: 'week', label: 'Last week' }, - { value: 'month', label: 'Last month' }, - { value: '3months', label: 'Last 3 months' }, - { value: 'year', label: 'Last year' }, -]; - -const WIDGET_HEADER_HEIGHT = 24; - -const styles = { - root: { - display: 'flex', - flexDirection: 'column', - width: '100%', - height: '100%', - minHeight: 0, - boxSizing: 'border-box', - padding: 12, - gap: 12, - fontFamily: THEME.fontFamily, - color: THEME.fontPrimary, - background: THEME.bgSecondary, - overflow: 'hidden', - } as const, - header: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - gap: 8, - flexWrap: 'wrap', - padding: '8px 12px', - background: THEME.bgPrimary, - border: `1px solid ${THEME.borderLight}`, - borderRadius: THEME.borderRadius, - boxSizing: 'border-box', - flexShrink: 0, - } as const, - contributor: { - display: 'flex', - alignItems: 'center', - gap: 8, - minWidth: 0, - } as const, - avatar: { - width: 24, - height: 24, - borderRadius: '50%', - objectFit: 'cover', - background: THEME.bgTertiary, - flexShrink: 0, - } as const, - smallAvatar: { - width: 16, - height: 16, - borderRadius: '50%', - objectFit: 'cover', - background: THEME.bgTertiary, - flexShrink: 0, - } as const, - nameCol: { - display: 'flex', - alignItems: 'baseline', - minWidth: 0, - gap: 6, - lineHeight: 1.2, - } as const, - name: { - fontSize: 14, - fontWeight: 500, - color: THEME.fontPrimary, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - } as const, - login: { - fontSize: 12, - fontWeight: 400, - color: THEME.fontTertiary, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - } as const, - controls: { - display: 'flex', - alignItems: 'center', - gap: 6, - flexWrap: 'wrap', - } as const, - select: { - fontFamily: THEME.fontFamily, - fontSize: 13, - height: 28, - lineHeight: '26px', - padding: '0 24px 0 10px', - border: `1px solid ${THEME.borderMedium}`, - borderRadius: THEME.borderRadius, - background: `${THEME.bgPrimary} url("data:image/svg+xml;utf8,") no-repeat right 8px center`, - color: THEME.fontPrimary, - cursor: 'pointer', - outline: 'none', - appearance: 'none', - WebkitAppearance: 'none', - MozAppearance: 'none', - } as const, - buttonNeutral: { - fontFamily: THEME.fontFamily, - fontSize: 13, - fontWeight: 500, - height: 28, - lineHeight: '26px', - padding: '0 10px', - border: `1px solid ${THEME.borderMedium}`, - borderRadius: THEME.borderRadius, - background: THEME.bgPrimary, - color: THEME.fontPrimary, - cursor: 'pointer', - outline: 'none', - } as const, - body: { - display: 'flex', - flexDirection: 'column', - flex: 1, - minHeight: 0, - gap: 12, - } as const, - chartCard: { - display: 'flex', - flexDirection: 'column', - flex: 1, - minHeight: 0, - border: `1px solid ${THEME.borderLight}`, - borderRadius: THEME.borderRadius, - padding: 8, - background: THEME.bgPrimary, - boxSizing: 'border-box', - } as const, - chartHeader: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - height: WIDGET_HEADER_HEIGHT, - padding: '0 4px', - flexShrink: 0, - } as const, - chartTitle: { - fontSize: 13, - fontWeight: 500, - color: THEME.fontPrimary, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - } as const, - chartTotal: { - fontSize: 13, - fontWeight: 500, - color: THEME.fontTertiary, - fontVariantNumeric: 'tabular-nums', - flexShrink: 0, - } as const, - chartContainer: { - flex: '1 1 0%', - minHeight: 90, - width: '100%', - position: 'relative', - marginTop: 4, - } as const, - centered: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - flex: 1, - color: THEME.fontTertiary, - fontSize: 13, - textAlign: 'center', - padding: 16, - } as const, - searchWrapper: { - display: 'flex', - flexDirection: 'column', - gap: 8, - flex: 1, - minHeight: 0, - } as const, - searchInput: { - fontFamily: THEME.fontFamily, - fontSize: 13, - height: 28, - padding: '0 10px', - border: `1px solid ${THEME.borderMedium}`, - borderRadius: THEME.borderRadius, - outline: 'none', - background: THEME.bgPrimary, - color: THEME.fontPrimary, - } as const, - searchList: { - display: 'flex', - flexDirection: 'column', - flex: 1, - minHeight: 0, - overflowY: 'auto', - border: `1px solid ${THEME.borderLight}`, - borderRadius: THEME.borderRadius, - background: THEME.bgPrimary, - } as const, - searchItem: { - display: 'flex', - alignItems: 'center', - gap: 8, - padding: '6px 10px', - cursor: 'pointer', - borderBottom: `1px solid ${THEME.borderLight}`, - fontSize: 13, - color: THEME.fontPrimary, - } as const, - truncatedHint: { - fontSize: 11, - color: THEME.fontTertiary, - marginTop: 4, - paddingLeft: 4, - } as const, -}; - -type BarChartProps = { - buckets: Bucket[]; - valueKey: 'prAuthored' | 'prMerged' | 'prReviewed'; - color: string; -}; - -const CHART_PADDING = { top: 6, right: 4, bottom: 16, left: 22 }; - -const BarChart = ({ buckets, valueKey, color }: BarChartProps) => { - const yMax = useMemo(() => { - const m = buckets.reduce((acc, b) => Math.max(acc, b[valueKey]), 0); - if (m === 0) return 1; - return m <= 5 ? m : Math.ceil(m / 5) * 5; - }, [buckets, valueKey]); - - const yTicks = useMemo(() => [0, Math.round(yMax / 2), yMax], [yMax]); - - const n = buckets.length; - const labelEvery = (() => { - if (n <= 6) return 1; - if (n <= 14) return 2; - return Math.ceil(n / 6); - })(); - - return ( -
-
- {yTicks.map((t) => { - const bottomPct = (t / yMax) * 100; - return ( -
- {t} -
- ); - })} -
- -
- {yTicks.map((t, i) => { - const bottomPct = (t / yMax) * 100; - return ( -
- ); - })} - -
- {buckets.map((b, i) => { - const v = b[valueKey]; - const heightPct = (v / yMax) * 100; - const showLabel = i % labelEvery === 0 || i === n - 1; - return ( -
-
0 ? 2 : 0, - background: color, - borderRadius: '2px 2px 0 0', - }} - /> - {showLabel && ( -
- {b.label} -
- )} -
- ); - })} -
-
-
- ); -}; - -const ContributorStats = () => { - const recordId = useRecordId(); - const [period, setPeriod] = useState('month'); - const [selectedId, setSelectedId] = useState(recordId); - const [stats, setStats] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const [searchQuery, setSearchQuery] = useState(''); - const [searchResults, setSearchResults] = useState([]); - const [searchLoading, setSearchLoading] = useState(false); - - useEffect(() => { - setSelectedId(recordId); - }, [recordId]); - - useEffect(() => { - if (selectedId) return; - let cancelled = false; - setSearchLoading(true); - const handle = setTimeout(async () => { - try { - const res = (await callAppRoute('/contributors/search', { - query: searchQuery, - limit: 20, - })) as SearchResponse; - if (!cancelled) setSearchResults(res.contributors ?? []); - } catch (err) { - if (!cancelled) { - enqueueSnackbar({ - message: - err instanceof Error ? err.message : 'Failed to search contributors', - variant: 'error', - }); - } - } finally { - if (!cancelled) setSearchLoading(false); - } - }, 200); - return () => { - cancelled = true; - clearTimeout(handle); - }; - }, [selectedId, searchQuery]); - - useEffect(() => { - if (!selectedId) { - setStats(null); - return; - } - let cancelled = false; - setLoading(true); - setError(null); - (async () => { - try { - const res = (await callAppRoute('/contributors/stats', { - contributorId: selectedId, - period, - })) as StatsResponse; - if (cancelled) return; - if (res.error) { - setError(res.error); - setStats(null); - } else { - setStats(res); - } - } catch (err) { - if (!cancelled) { - setError(err instanceof Error ? err.message : 'Failed to load stats'); - setStats(null); - } - } finally { - if (!cancelled) setLoading(false); - } - })(); - return () => { - cancelled = true; - }; - }, [selectedId, period]); - - const renderHeader = (contributor: ContributorInfo | null) => ( -
-
- {contributor?.avatarUrl ? ( - - ) : ( -
- )} -
- - {contributor?.name ?? contributor?.ghLogin ?? 'Contributor stats'} - - {contributor?.ghLogin && - contributor.ghLogin !== contributor.name && ( - @{contributor.ghLogin} - )} -
-
-
- {selectedId && !recordId && ( - - )} - -
-
- ); - - if (!selectedId) { - return ( -
- {renderHeader(null)} -
- -
- {searchLoading && searchResults.length === 0 && ( -
- Searching... -
- )} - {!searchLoading && searchResults.length === 0 && ( -
- No contributors found. -
- )} - {searchResults.map((eng) => ( -
setSelectedId(eng.id)} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') setSelectedId(eng.id); - }} - style={styles.searchItem} - > - {eng.avatarUrl ? ( - - ) : ( -
- )} -
- - {eng.name ?? eng.ghLogin ?? 'Unknown'} - - {eng.ghLogin && eng.ghLogin !== eng.name && ( - @{eng.ghLogin} - )} -
-
- ))} -
-
-
- ); - } - - return ( -
- {renderHeader(stats?.contributor ?? null)} -
- {loading && !stats && ( -
Loading contributor stats...
- )} - {error &&
{error}
} - {stats && ( - <> -
-
- PRs authored (merged) - {stats.totals.prAuthored} -
- - {stats.truncated.prAuthored && ( - - Showing partial data (results truncated). - - )} -
- -
-
- PRs merged - {stats.totals.prMerged} -
- - {stats.truncated.prMerged && ( - - Showing partial data (results truncated). - - )} -
- -
-
- PRs reviewed - {stats.totals.prReviewed} -
- - {stats.truncated.prReviewed && ( - - Showing partial data (results truncated). - - )} -
- - )} -
-
- ); -}; - -export default defineFrontComponent({ - universalIdentifier: '6c2f1a8d-4b9e-4f7a-9c5d-1e8b2a3d4c5f', - name: 'Contributor Stats', - description: - 'Displays time-bucketed charts of PRs authored (merged only), merged and reviewed by a contributor over the last week, month, 3 months or year.', - component: ContributorStats, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/fetch-contributors.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/fetch-contributors.front-component.tsx deleted file mode 100644 index a3d70ae079..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/fetch-contributors.front-component.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useEffect, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - enqueueSnackbar, - unmountFrontComponent, - updateProgress, -} from 'twenty-sdk/front-component'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; - -type CountResponse = { - totalPages: number; - repos: Array<{ owner: string; repo: string; totalCount: number; pages: number }>; -}; - -type FetchPageResponse = { - contributorCount: number; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type SyncStatus = 'syncing' | 'done' | 'error'; - -const FetchContributors = () => { - const [status, setStatus] = useState('syncing'); - - useEffect(() => { - const run = async () => { - try { - const counts = (await callAppRoute( - '/github/count-contributors', - {}, - )) as CountResponse; - - if (counts.repos.length === 0) { - throw new Error( - 'No repos resolved. Set GITHUB_REPOS in the application variables.', - ); - } - - const totalPages = Math.max(counts.totalPages, 1); - let pagesProcessed = 0; - let totalSynced = 0; - - for (const { owner, repo } of counts.repos) { - let cursor: string | null = null; - let hasMore = true; - - while (hasMore) { - const data = (await callAppRoute('/github/fetch-contributors', { - owner, - repo, - cursor, - })) as FetchPageResponse; - - totalSynced += data.contributorCount; - hasMore = data.hasMore && data.contributorCount > 0; - cursor = data.endCursor; - pagesProcessed++; - - updateProgress( - Math.min(Math.round((pagesProcessed / totalPages) * 100), 99), - ); - } - } - - updateProgress(100); - enqueueSnackbar({ - message: `Synced ${totalSynced} contributors`, - variant: 'success', - }); - setStatus('done'); - } catch (err) { - const message = - err instanceof Error ? err.message : 'Failed to fetch contributors'; - enqueueSnackbar({ message, variant: 'error' }); - setStatus('error'); - } finally { - unmountFrontComponent(); - } - }; - run(); - }, []); - - if (status === 'syncing') return
Fetching contributors...
; - if (status === 'error') return
Failed to fetch contributors.
; - return
Done
; -}; - -export default defineFrontComponent({ - universalIdentifier: '08f40f82-24ed-4f3e-8c99-695151e90e38', - name: 'Fetch Contributors', - description: - 'Fetches contributors from every configured GitHub repo (GITHUB_REPOS) and upserts them as Contributor records.', - isHeadless: true, - component: FetchContributors, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/top-pr-authors.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/top-pr-authors.front-component.tsx deleted file mode 100644 index 9818b1e28b..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/top-pr-authors.front-component.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useEffect, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { enqueueSnackbar } from 'twenty-sdk/front-component'; -import { - Leaderboard, - type LeaderboardEntry, -} from 'src/modules/github/contributor/components/leaderboard'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; - -type Response = { - topAuthors: LeaderboardEntry[]; -}; - -const TopPRAuthors = () => { - const [entries, setEntries] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - let cancelled = false; - setLoading(true); - setError(null); - (async () => { - try { - const res = (await callAppRoute('/contributors/top', { - days: 90, - limit: 20, - kind: 'authors', - })) as Response; - if (!cancelled) setEntries(res.topAuthors ?? []); - } catch (err) { - if (!cancelled) { - const message = - err instanceof Error ? err.message : 'Failed to load top authors'; - setError(message); - enqueueSnackbar({ message, variant: 'error' }); - } - } finally { - if (!cancelled) setLoading(false); - } - })(); - return () => { - cancelled = true; - }; - }, []); - - return ( - - ); -}; - -export const TOP_PR_AUTHORS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = - 'a1d4f7e2-9b3c-4e8a-bf21-5d6c8a9b2e3f'; - -export default defineFrontComponent({ - universalIdentifier: TOP_PR_AUTHORS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - name: 'Top PR Authors', - description: 'Leaderboard of the top 20 pull-request authors over the last 90 days.', - component: TopPRAuthors, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/top-reviewers.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/top-reviewers.front-component.tsx deleted file mode 100644 index 457a682b24..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/front-components/top-reviewers.front-component.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useEffect, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { enqueueSnackbar } from 'twenty-sdk/front-component'; -import { - Leaderboard, - type LeaderboardEntry, -} from 'src/modules/github/contributor/components/leaderboard'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; - -type Response = { - topReviewers: LeaderboardEntry[]; -}; - -const TopReviewers = () => { - const [entries, setEntries] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - let cancelled = false; - setLoading(true); - setError(null); - (async () => { - try { - const res = (await callAppRoute('/contributors/top', { - days: 90, - limit: 20, - kind: 'reviewers', - })) as Response; - if (!cancelled) setEntries(res.topReviewers ?? []); - } catch (err) { - if (!cancelled) { - const message = - err instanceof Error ? err.message : 'Failed to load top reviewers'; - setError(message); - enqueueSnackbar({ message, variant: 'error' }); - } - } finally { - if (!cancelled) setLoading(false); - } - })(); - return () => { - cancelled = true; - }; - }, []); - - return ( - - ); -}; - -export const TOP_REVIEWERS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = - 'b2e5f8a3-ac4d-4f9b-8032-6e7d9bac3f4a'; - -export default defineFrontComponent({ - universalIdentifier: TOP_REVIEWERS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - name: 'Top Reviewers', - description: 'Leaderboard of the top 20 pull-request reviewers over the last 90 days.', - component: TopReviewers, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/github/count-contributors.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/github/count-contributors.ts deleted file mode 100644 index 343c1f0b83..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/github/count-contributors.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { githubGraphql } from 'src/modules/github/connector/github-client'; - -const QUERY = ` -query($owner: String!, $name: String!) { - repository(owner: $owner, name: $name) { - mentionableUsers { totalCount } - } -}`; - -type Response = { - repository: { mentionableUsers: { totalCount: number } } | null; -}; - -export async function countContributors( - owner: string, - name: string, -): Promise { - const data = await githubGraphql(QUERY, { owner, name }); - return data.repository?.mentionableUsers.totalCount ?? 0; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/github/fetch-contributors.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/github/fetch-contributors.ts deleted file mode 100644 index 78c489eb92..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/github/fetch-contributors.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - EMPTY_PAGE, - type GithubPage, - githubGraphql, -} from 'src/modules/github/connector/github-client'; - -const QUERY = ` -query($owner: String!, $name: String!, $cursor: String) { - repository(owner: $owner, name: $name) { - mentionableUsers(first: 100, after: $cursor) { - totalCount - pageInfo { hasNextPage endCursor } - nodes { - login - databaseId - avatarUrl - } - } - } -}`; - -export type GqlContributor = { - login: string; - databaseId: number; - avatarUrl: string; -}; - -type Response = { - repository: { mentionableUsers: GithubPage } | null; -}; - -export async function fetchContributors( - owner: string, - name: string, - cursor: string | null = null, -): Promise<{ - contributors: GqlContributor[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}> { - const data = await githubGraphql(QUERY, { owner, name, cursor }); - const conn = data.repository?.mentionableUsers; - if (!conn) return { contributors: [], ...EMPTY_PAGE }; - - return { - contributors: conn.nodes, - totalCount: conn.totalCount, - hasMore: conn.pageInfo.hasNextPage, - endCursor: conn.pageInfo.endCursor, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/mutations/batch-upsert.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/mutations/batch-upsert.ts deleted file mode 100644 index b6f3f7e867..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/mutations/batch-upsert.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ContributorRow } from 'src/modules/github/contributor/types/contributor-row'; -import { chunkedBatchCreate } from 'src/modules/shared/twenty-client'; - -export async function batchUpsertContributors( - items: Array<{ - ghLogin: string; - name: string; - githubId: number; - avatarUrl?: { primaryLinkLabel: string; primaryLinkUrl: string; secondaryLinks: null } | null; - contributions?: number; - }>, -): Promise { - return chunkedBatchCreate('createContributors', items, { - id: true, - ghLogin: true, - name: true, - githubId: true, - avatarUrl: true, - contributions: true, - }) as Promise; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/queries/find-by-gh-login.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/queries/find-by-gh-login.ts deleted file mode 100644 index 9ee5b6a64b..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/queries/find-by-gh-login.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { ContributorRow } from 'src/modules/github/contributor/types/contributor-row'; -import { getClient } from 'src/modules/shared/twenty-client'; - -export async function findContributorByGhLogin( - ghLogin: string, -): Promise { - const client = getClient(); - const res = await client.query({ - contributors: { - __args: { - filter: { ghLogin: { eq: ghLogin } }, - first: 1, - }, - edges: { - node: { - id: true, - ghLogin: true, - name: true, - githubId: true, - }, - }, - }, - }); - - const edges = res.contributors?.edges; - return (edges?.[0]?.node as ContributorRow | undefined) ?? null; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/queries/search-contributors.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/queries/search-contributors.ts deleted file mode 100644 index 5c45ba376c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/graphql/queries/search-contributors.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { getClient } from 'src/modules/shared/twenty-client'; - -export type ContributorSearchResult = { - id: string; - name: string | null; - ghLogin: string | null; - avatarUrl: string | null; -}; - -export async function searchContributors( - query: string, - limit: number, -): Promise { - const client = getClient(); - - const filter = - query.length === 0 - ? undefined - : { - or: [ - { name: { ilike: `%${query}%` } }, - { ghLogin: { ilike: `%${query}%` } }, - ], - }; - - const res = await client.query({ - contributors: { - __args: { - ...(filter ? { filter } : {}), - orderBy: [{ name: 'AscNullsLast' }], - first: limit, - }, - edges: { - node: { - id: true, - name: true, - ghLogin: true, - avatarUrl: { primaryLinkUrl: true }, - }, - }, - }, - }); - - const edges = res.contributors?.edges ?? []; - - return edges.map((e) => ({ - id: e.node.id, - name: e.node.name ?? null, - ghLogin: e.node.ghLogin ?? null, - avatarUrl: e.node.avatarUrl?.primaryLinkUrl ?? null, - })); -} 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 deleted file mode 100644 index 4ebb1dfab4..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/contributor-stats.logic-function.ts +++ /dev/null @@ -1,389 +0,0 @@ -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'; - -type Granularity = 'day' | 'week' | 'month'; - -type Bucket = { - key: string; - label: string; - start: string; - end: string; - prAuthored: number; - prMerged: number; - prReviewed: number; -}; - -type Totals = { - prAuthored: number; - prMerged: number; - prReviewed: number; -}; - -type ContributorInfo = { - id: string; - name: string | null; - ghLogin: string | null; - avatarUrl: string | null; -}; - -type ContributorStatsPayload = { - contributorId?: string; - period?: StatsPeriod; -}; - -type ContributorStatsResponse = - | { - contributor: ContributorInfo; - period: StatsPeriod; - granularity: Granularity; - buckets: Bucket[]; - totals: Totals; - truncated: { - prAuthored: boolean; - prMerged: boolean; - prReviewed: boolean; - }; - } - | { error: string }; - -const PAGE_SIZE = 100; -const MAX_PAGES = 30; - -const PERIOD_CONFIG: Record< - StatsPeriod, - { 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, - }, - '3months': { - granularity: 'week', - rangeMs: 13 * 7 * 24 * 3600 * 1000, - bucketCount: 13, - }, - year: { - granularity: 'month', - rangeMs: 365 * 24 * 3600 * 1000, - bucketCount: 12, - }, -}; - -const startOfUtcDay = (d: Date): Date => - new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); - -const startOfUtcWeek = (d: Date): Date => { - const day = startOfUtcDay(d); - const dow = day.getUTCDay(); - const diff = (dow + 6) % 7; - day.setUTCDate(day.getUTCDate() - diff); - return day; -}; - -const startOfUtcMonth = (d: Date): Date => - new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1)); - -const addBucket = (start: Date, granularity: Granularity, n: number): Date => { - const out = new Date(start); - if (granularity === 'day') out.setUTCDate(out.getUTCDate() + n); - else if (granularity === 'week') out.setUTCDate(out.getUTCDate() + 7 * n); - else out.setUTCMonth(out.getUTCMonth() + n); - return out; -}; - -const bucketStartFor = (d: Date, granularity: Granularity): Date => { - if (granularity === 'day') return startOfUtcDay(d); - if (granularity === 'week') return startOfUtcWeek(d); - return startOfUtcMonth(d); -}; - -const formatBucketLabel = (start: Date, granularity: Granularity): string => { - const month = start.toLocaleString('en-US', { - month: 'short', - timeZone: 'UTC', - }); - if (granularity === 'month') { - return `${month} ${String(start.getUTCFullYear()).slice(2)}`; - } - return `${month} ${start.getUTCDate()}`; -}; - -const buildBuckets = ( - now: Date, - period: StatsPeriod, -): { buckets: Bucket[]; rangeStart: Date; granularity: Granularity } => { - const { granularity, bucketCount } = PERIOD_CONFIG[period]; - const lastBucketStart = bucketStartFor(now, granularity); - const firstBucketStart = addBucket( - lastBucketStart, - granularity, - -(bucketCount - 1), - ); - - const buckets: Bucket[] = []; - for (let i = 0; i < bucketCount; i++) { - const start = addBucket(firstBucketStart, granularity, i); - const end = addBucket(start, granularity, 1); - buckets.push({ - key: start.toISOString(), - label: formatBucketLabel(start, granularity), - start: start.toISOString(), - end: end.toISOString(), - prAuthored: 0, - prMerged: 0, - prReviewed: 0, - }); - } - return { buckets, rangeStart: firstBucketStart, granularity }; -}; - -type Edge = { node: T }; -type Connection = { - edges: Edge[]; - pageInfo: { hasNextPage: boolean; endCursor: string | null }; -}; - -async function paginateUntil( - fetchPage: (cursor: string | null) => Promise>, - isOlderThanRange: (item: T) => boolean, -): Promise<{ items: T[]; truncated: boolean }> { - const items: T[] = []; - let cursor: string | null = null; - for (let page = 0; page < MAX_PAGES; page++) { - const conn = await fetchPage(cursor); - let stop = false; - for (const edge of conn.edges) { - if (isOlderThanRange(edge.node)) { - stop = true; - break; - } - items.push(edge.node); - } - if (stop) return { items, truncated: false }; - if (!conn.pageInfo.hasNextPage || !conn.pageInfo.endCursor) { - return { items, truncated: false }; - } - cursor = conn.pageInfo.endCursor; - } - return { items, truncated: true }; -} - -type MergedPrNode = { mergedAt: string | null }; -type ReviewNode = { firstSubmittedAt: string | null }; - -const fetchContributorInfo = async ( - contributorId: string, -): Promise => { - const client = getClient(); - const res = await client.query({ - contributors: { - __args: { filter: { id: { eq: contributorId } }, first: 1 }, - edges: { - node: { - id: true, - name: true, - ghLogin: true, - avatarUrl: { primaryLinkUrl: true }, - }, - }, - }, - }); - const node = res.contributors?.edges?.[0]?.node; - if (!node) return null; - return { - id: node.id, - name: node.name ?? null, - ghLogin: node.ghLogin ?? null, - avatarUrl: node.avatarUrl?.primaryLinkUrl ?? null, - }; -}; - -const handler = async ( - event: RoutePayload, -): Promise => { - const contributorId = event.body?.contributorId; - const period: StatsPeriod = event.body?.period ?? 'month'; - - if (!contributorId) { - return { error: 'contributorId is required' }; - } - if (!(period in PERIOD_CONFIG)) { - return { error: `Unsupported period: ${period}` }; - } - - const contributor = await fetchContributorInfo(contributorId); - if (!contributor) { - return { error: 'Contributor not found' }; - } - - const now = new Date(); - const { buckets, rangeStart, granularity } = buildBuckets(now, period); - const rangeStartMs = rangeStart.getTime(); - const bucketIndex = new Map(); - buckets.forEach((b, i) => bucketIndex.set(b.key, i)); - - const client = getClient(); - - const mergedResult = await paginateUntil( - async (cursor) => { - const res = await client.query({ - pullRequests: { - __args: { - filter: { - and: [ - { mergerId: { eq: contributorId } }, - { state: { eq: 'MERGED' } }, - ], - }, - orderBy: [{ mergedAt: 'DescNullsLast' }], - first: PAGE_SIZE, - after: cursor, - }, - edges: { node: { mergedAt: true } }, - pageInfo: { hasNextPage: true, endCursor: true }, - }, - }); - return ( - (res.pullRequests as Connection) ?? { - edges: [], - pageInfo: { hasNextPage: false, endCursor: null }, - } - ); - }, - (n) => { - if (!n.mergedAt) return false; - return new Date(n.mergedAt).getTime() < rangeStartMs; - }, - ); - - const authoredResult = await paginateUntil( - async (cursor) => { - const res = await client.query({ - pullRequests: { - __args: { - filter: { - and: [ - { authorId: { eq: contributorId } }, - { state: { eq: 'MERGED' } }, - ], - }, - orderBy: [{ mergedAt: 'DescNullsLast' }], - first: PAGE_SIZE, - after: cursor, - }, - edges: { node: { mergedAt: true } }, - pageInfo: { hasNextPage: true, endCursor: true }, - }, - }); - return ( - (res.pullRequests as Connection) ?? { - edges: [], - pageInfo: { hasNextPage: false, endCursor: null }, - } - ); - }, - (n) => { - if (!n.mergedAt) return false; - return new Date(n.mergedAt).getTime() < rangeStartMs; - }, - ); - - const reviewedResult = await paginateUntil( - async (cursor) => { - const res = await client.query({ - pullRequestReviews: { - __args: { - filter: { - and: [ - { reviewerId: { eq: contributorId } }, - { isSelfReview: { eq: false } }, - ], - }, - orderBy: [{ firstSubmittedAt: 'DescNullsLast' }], - first: PAGE_SIZE, - after: cursor, - }, - edges: { node: { firstSubmittedAt: true } }, - 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() < rangeStartMs; - }, - ); - - const totals: Totals = { prAuthored: 0, prMerged: 0, prReviewed: 0 }; - - for (const pr of authoredResult.items) { - if (!pr.mergedAt) continue; - const d = new Date(pr.mergedAt); - if (d.getTime() < rangeStartMs) continue; - const key = bucketStartFor(d, granularity).toISOString(); - const idx = bucketIndex.get(key); - if (idx === undefined) continue; - buckets[idx].prAuthored++; - totals.prAuthored++; - } - - for (const pr of mergedResult.items) { - if (!pr.mergedAt) continue; - const d = new Date(pr.mergedAt); - if (d.getTime() < rangeStartMs) continue; - const key = bucketStartFor(d, granularity).toISOString(); - const idx = bucketIndex.get(key); - if (idx === undefined) continue; - buckets[idx].prMerged++; - totals.prMerged++; - } - - for (const r of reviewedResult.items) { - if (!r.firstSubmittedAt) continue; - const d = new Date(r.firstSubmittedAt); - if (d.getTime() < rangeStartMs) continue; - const key = bucketStartFor(d, granularity).toISOString(); - const idx = bucketIndex.get(key); - if (idx === undefined) continue; - buckets[idx].prReviewed++; - totals.prReviewed++; - } - - return { - contributor, - period, - granularity, - buckets, - totals, - truncated: { - prAuthored: authoredResult.truncated, - prMerged: mergedResult.truncated, - prReviewed: reviewedResult.truncated, - }, - }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a3c9e1b6-2f47-4d8a-9b0f-7e6d1a2c3b4f', - name: 'contributor-stats', - description: - 'Returns time-bucketed counts of PRs authored (merged only), merged and reviewed by a contributor over the selected period.', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/contributors/stats', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 5704f40049..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/count-contributors.logic-function.ts +++ /dev/null @@ -1,25 +0,0 @@ -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'; - -type CountContributorsPayload = { - repos?: string[]; -}; - -const handler = async (event: RoutePayload) => - countAcrossRepos(event.body?.repos, countContributors, 'count-contributors'); - -export default defineLogicFunction({ - universalIdentifier: 'fe0a6f00-0d63-4cb9-9b3c-1d8186181830', - name: 'count-contributors', - description: - 'Counts contributors across configured repos and returns the per-repo page split.', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/github/count-contributors', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 0a5128aa52..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/fetch-contributors.logic-function.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { defineLogicFunction } from 'twenty-sdk/define'; -import { type RoutePayload } from 'twenty-sdk/logic-function'; -import { - fetchContributors, - type GqlContributor, -} from 'src/modules/github/contributor/graphql/github/fetch-contributors'; -import { batchUpsertContributors } from 'src/modules/github/contributor/graphql/mutations/batch-upsert'; -import { isFixtureAllowed } from 'src/modules/shared/fixtures'; - -export type FetchContributorsFixturePage = { - contributors: GqlContributor[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type FetchContributorsPayload = { - owner: string; - repo: string; - cursor?: string | null; - fixturePage?: FetchContributorsFixturePage; -}; - -const handler = async (event: RoutePayload) => { - const { owner, repo, cursor = null, fixturePage } = event.body ?? {}; - if (!owner || !repo) { - return { error: 'owner and repo are required' }; - } - - const result = - fixturePage && isFixtureAllowed() - ? fixturePage - : await fetchContributors(owner, repo, cursor); - const { contributors, totalCount, hasMore, endCursor } = result; - - if (contributors.length === 0) { - return { contributorCount: 0, totalCount, hasMore: false, endCursor: null }; - } - - const contributorData = contributors.map((c: GqlContributor) => ({ - ghLogin: c.login, - name: c.login, - githubId: c.databaseId ?? 0, - avatarUrl: c.avatarUrl - ? { - primaryLinkLabel: c.login, - primaryLinkUrl: c.avatarUrl, - secondaryLinks: null, - } - : null, - })); - - await batchUpsertContributors(contributorData); - - return { - contributorCount: contributors.length, - totalCount, - hasMore, - endCursor, - }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'ed9ba981-4172-4924-a18f-51fc6dcbcb23', - name: 'fetch-contributors', - description: - 'Fetches one page of contributors via GitHub GraphQL API and batch upserts them into the workspace.', - timeoutSeconds: 300, - handler, - httpRouteTriggerSettings: { - path: '/github/fetch-contributors', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 362fc64c04..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/search-contributors.logic-function.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { defineLogicFunction } from 'twenty-sdk/define'; -import { type RoutePayload } from 'twenty-sdk/logic-function'; -import { - searchContributors, - type ContributorSearchResult, -} from 'src/modules/github/contributor/graphql/queries/search-contributors'; - -type SearchContributorsPayload = { - query?: string; - limit?: number; -}; - -const DEFAULT_LIMIT = 20; -const MAX_LIMIT = 50; - -const handler = async ( - event: RoutePayload, -): Promise<{ contributors: ContributorSearchResult[] }> => { - const queryInput = event.body?.query; - const rawQuery = typeof queryInput === 'string' ? queryInput.trim() : ''; - const limitInput = event.body?.limit; - const limitNumber = - typeof limitInput === 'number' && Number.isFinite(limitInput) - ? limitInput - : DEFAULT_LIMIT; - const limit = Math.min(Math.max(Math.floor(limitNumber), 1), MAX_LIMIT); - - const contributors = await searchContributors(rawQuery, limit); - return { contributors }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'b4d8f2a7-3e58-4f9b-ac1d-8f7e2b3c4d5e', - name: 'search-contributors', - description: - 'Searches contributors by name or GitHub login for use in front-end pickers.', - timeoutSeconds: 15, - handler, - httpRouteTriggerSettings: { - path: '/contributors/search', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 5323e896cc..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/logic-functions/top-contributors.logic-function.ts +++ /dev/null @@ -1,266 +0,0 @@ -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'; - -type ContributorRef = { - id: string; - name: string | null; - ghLogin: string | null; - avatarUrl: string | null; -}; - -type LeaderboardEntry = ContributorRef & { - count: number; -}; - -type TopContributorsKind = 'authors' | 'reviewers' | 'both'; - -type TopContributorsPayload = { - days?: number; - limit?: number; - kind?: TopContributorsKind; -}; - -type TopContributorsResponse = { - days: number; - limit: number; - topAuthors: LeaderboardEntry[]; - topReviewers: LeaderboardEntry[]; - truncated: { authors: boolean; reviewers: boolean }; -}; - -const PAGE_SIZE = 100; -const MAX_PAGES = 50; -const DEFAULT_DAYS = 90; -const DEFAULT_LIMIT = 10; -const MAX_LIMIT = 50; -const MAX_DAYS = 365; - -type Edge = { node: T }; -type Connection = { - edges: Edge[]; - pageInfo: { hasNextPage: boolean; endCursor: string | null }; -}; - -async function paginateUntil( - fetchPage: (cursor: string | null) => Promise>, - isOlderThanRange: (item: T) => boolean, -): Promise<{ items: T[]; truncated: boolean }> { - const items: T[] = []; - let cursor: string | null = null; - for (let page = 0; page < MAX_PAGES; page++) { - const conn = await fetchPage(cursor); - let stop = false; - for (const edge of conn.edges) { - if (isOlderThanRange(edge.node)) { - stop = true; - break; - } - items.push(edge.node); - } - if (stop) return { items, truncated: false }; - if (!conn.pageInfo.hasNextPage || !conn.pageInfo.endCursor) { - return { items, truncated: false }; - } - cursor = conn.pageInfo.endCursor; - } - return { items, truncated: true }; -} - -type AuthorInfo = { - id: string; - name: string | null; - ghLogin: string | null; - avatarUrl: { primaryLinkUrl: string | null } | null; -}; - -type PrNode = { - githubCreatedAt: string | null; - author: AuthorInfo | null; -}; - -type ReviewNode = { - firstSubmittedAt: string | null; - reviewer: AuthorInfo | null; -}; - -const tally = ( - items: { contributor: AuthorInfo | null }[], - limit: number, -): LeaderboardEntry[] => { - const counts = new Map(); - for (const item of items) { - const c = item.contributor; - if (!c) continue; - if (isBotLogin(c.ghLogin)) continue; - const existing = counts.get(c.id); - if (existing) { - existing.count += 1; - } else { - counts.set(c.id, { - id: c.id, - name: c.name ?? null, - ghLogin: c.ghLogin ?? null, - avatarUrl: c.avatarUrl?.primaryLinkUrl ?? null, - count: 1, - }); - } - } - return Array.from(counts.values()) - .sort( - (a, b) => - b.count - a.count || (a.ghLogin ?? '').localeCompare(b.ghLogin ?? ''), - ) - .slice(0, limit); -}; - -const handler = async ( - event: RoutePayload, -): Promise => { - const daysInput = event.body?.days; - const limitInput = event.body?.limit; - - const days = Math.min( - Math.max( - Math.floor( - typeof daysInput === 'number' && Number.isFinite(daysInput) - ? daysInput - : DEFAULT_DAYS, - ), - 1, - ), - MAX_DAYS, - ); - const limit = Math.min( - Math.max( - Math.floor( - typeof limitInput === 'number' && Number.isFinite(limitInput) - ? limitInput - : DEFAULT_LIMIT, - ), - 1, - ), - MAX_LIMIT, - ); - - const kindInput = event.body?.kind; - const kind: TopContributorsKind = - kindInput === 'authors' || kindInput === 'reviewers' ? kindInput : 'both'; - - const sinceMs = Date.now() - days * 24 * 3600 * 1000; - 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 }, - }, - }, - }, - 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; - }, - ); - - const reviewedResult: { items: ReviewNode[]; truncated: boolean } = - kind === 'authors' - ? { items: [], truncated: false } - : await paginateUntil( - async (cursor) => { - const res = await client.query({ - pullRequestReviews: { - __args: { - filter: { isSelfReview: { eq: false } }, - 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 }, - } - ); - }, - (n) => { - if (!n.firstSubmittedAt) return false; - return new Date(n.firstSubmittedAt).getTime() < sinceMs; - }, - ); - - const topAuthors = tally( - authoredResult.items.map((pr) => ({ contributor: pr.author })), - limit, - ); - const topReviewers = tally( - reviewedResult.items.map((r) => ({ contributor: r.reviewer })), - limit, - ); - - return { - days, - limit, - topAuthors, - topReviewers, - truncated: { - authors: authoredResult.truncated, - reviewers: reviewedResult.truncated, - }, - }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'd5b9c4a2-7e3f-4a1b-9c8d-2f4e6a7b8c9d', - name: 'top-contributors', - description: - 'Returns the top contributors by pull-request authorship and review counts over a configurable time window.', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/contributors/top', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/normalizers.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/normalizers.ts deleted file mode 100644 index 8462783aea..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/normalizers.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { type LinksFieldValue, toLinksField } from 'src/modules/shared/types'; - -export type GhUser = { - login: string; - id?: number; - databaseId?: number; - avatarUrl?: string | null; - avatar_url?: string | null; -}; - -export type ContributorCanonical = { - ghLogin: string; - name: string; - githubId: number; - avatarUrl: LinksFieldValue | null; -}; - -export function contributorFromGhUser(user: GhUser): ContributorCanonical { - const avatarUrl = user.avatarUrl ?? user.avatar_url ?? null; - return { - ghLogin: user.login, - name: user.login, - githubId: user.id ?? user.databaseId ?? 0, - avatarUrl: avatarUrl ? toLinksField(avatarUrl, user.login) : null, - }; -} - -export function dedupeContributors( - users: Array, -): ContributorCanonical[] { - const seen = new Map(); - for (const u of users) { - if (!u || !u.login) continue; - const existing = seen.get(u.login); - if (!existing) { - seen.set(u.login, contributorFromGhUser(u)); - continue; - } - if (!existing.avatarUrl) { - const avatarUrl = u.avatarUrl ?? u.avatar_url ?? null; - if (avatarUrl) { - existing.avatarUrl = toLinksField(avatarUrl, u.login); - } - } - } - return [...seen.values()]; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/objects/contributor.object.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/objects/contributor.object.ts deleted file mode 100644 index c0de548677..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/objects/contributor.object.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { defineObject, FieldType } from 'twenty-sdk/define'; - -export const CONTRIBUTOR_UNIVERSAL_IDENTIFIER = - '8e9464f0-fdc9-487a-9963-c7accac0d4bb'; - -export const CONTRIBUTOR_NAME_FIELD_UNIVERSAL_IDENTIFIER = - 'e2e77e0e-5300-44ce-ab1c-9ad0593b5d1a'; - -export const CONTRIBUTOR_GH_LOGIN_FIELD_UNIVERSAL_IDENTIFIER = - 'a1d3c7b2-4e5f-4a8b-9c6d-2e1f0b3a4c5d'; - -export const CONTRIBUTOR_GITHUB_ID_FIELD_UNIVERSAL_IDENTIFIER = - 'f7a8b9c0-1d2e-4f3a-8b5c-6d7e8f9a0b1c'; - -export const CONTRIBUTOR_AVATAR_URL_FIELD_UNIVERSAL_IDENTIFIER = - '450a612c-f7a3-4bba-aa36-e22339eb0720'; - -export const CONTRIBUTOR_CONTRIBUTIONS_FIELD_UNIVERSAL_IDENTIFIER = - '66f73e49-6edb-48fa-812b-c3e684ef6340'; - -export const CONTRIBUTOR_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'bb8d2a87-b41c-578b-98de-2e37dba12a14'; - -export default defineObject({ - universalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - nameSingular: 'contributor', - namePlural: 'contributors', - labelSingular: 'Contributor', - labelPlural: 'Contributors', - icon: 'IconUsers', - labelIdentifierFieldMetadataUniversalIdentifier: - CONTRIBUTOR_NAME_FIELD_UNIVERSAL_IDENTIFIER, - fields: [ - { - universalIdentifier: CONTRIBUTOR_NAME_FIELD_UNIVERSAL_IDENTIFIER, - name: 'name', - type: FieldType.TEXT, - label: 'Name', - icon: 'IconUser', - }, - { - universalIdentifier: CONTRIBUTOR_GH_LOGIN_FIELD_UNIVERSAL_IDENTIFIER, - name: 'ghLogin', - type: FieldType.TEXT, - label: 'GitHub Login', - icon: 'IconBrandGithub', - isUnique: true, - }, - { - universalIdentifier: CONTRIBUTOR_GITHUB_ID_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubId', - type: FieldType.NUMBER, - label: 'GitHub ID', - icon: 'IconHash', - }, - { - universalIdentifier: CONTRIBUTOR_AVATAR_URL_FIELD_UNIVERSAL_IDENTIFIER, - name: 'avatarUrl', - type: FieldType.LINKS, - label: 'Avatar', - icon: 'IconPhoto', - }, - { - universalIdentifier: CONTRIBUTOR_CONTRIBUTIONS_FIELD_UNIVERSAL_IDENTIFIER, - name: 'contributions', - type: FieldType.NUMBER, - label: 'Contributions', - icon: 'IconTrendingUp', - defaultValue: 0, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/types/contributor-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/types/contributor-row.ts deleted file mode 100644 index 0f4fbce38b..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/types/contributor-row.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; - -export type ContributorRow = { - id: string; - ghLogin?: string | null; - name?: string | null; - githubId?: number | null; - avatarUrl?: LinksFieldValue | null; - contributions?: number | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/utils/is-bot-login.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/utils/is-bot-login.ts deleted file mode 100644 index e5e031f3b1..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/utils/is-bot-login.ts +++ /dev/null @@ -1,30 +0,0 @@ -const BOT_LOGIN_SUFFIX = '[bot]'; - -const KNOWN_BOT_LOGINS = new Set([ - 'github-actions', - 'dependabot', - 'dependabot-preview', - 'renovate', - 'renovate-bot', - 'cubic-dev-ai', - 'greptile-apps', - 'sentry', - 'sentry-io', - 'codecov', - 'codecov-commenter', - 'snyk-bot', - 'mergify', - 'allcontributors', - 'imgbot', - 'pre-commit-ci', - 'semantic-release-bot', - 'stale', - 'web-flow', -]); - -export function isBotLogin(login: string | null | undefined): boolean { - if (!login) return false; - const lower = login.toLowerCase(); - if (lower.endsWith(BOT_LOGIN_SUFFIX)) return true; - return KNOWN_BOT_LOGINS.has(lower); -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/views/all-contributors.view.ts b/packages/twenty-apps/community/github-connector/src/modules/github/contributor/views/all-contributors.view.ts deleted file mode 100644 index 6346c1adad..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/contributor/views/all-contributors.view.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - defineView, - ViewKey, - ViewSortDirection, - ViewType, -} from 'twenty-sdk/define'; -import { - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - CONTRIBUTOR_NAME_FIELD_UNIVERSAL_IDENTIFIER, - CONTRIBUTOR_GH_LOGIN_FIELD_UNIVERSAL_IDENTIFIER, - CONTRIBUTOR_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/contributor/objects/contributor.object'; - -export const ALL_CONTRIBUTORS_VIEW_UNIVERSAL_IDENTIFIER = - 'e2b51f8e-97ea-49e0-b38d-a69d2191236f'; - -export default defineView({ - universalIdentifier: ALL_CONTRIBUTORS_VIEW_UNIVERSAL_IDENTIFIER, - name: 'All Contributors', - objectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - type: ViewType.TABLE, - icon: 'IconUsers', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'b53cb9dd-0e2e-415e-81af-80bedbed89bc', - fieldMetadataUniversalIdentifier: - CONTRIBUTOR_NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 200, - }, - { - universalIdentifier: '604a2f1d-ef20-4821-8742-fe3b5b83ffcf', - fieldMetadataUniversalIdentifier: - CONTRIBUTOR_GH_LOGIN_FIELD_UNIVERSAL_IDENTIFIER, - position: 1, - isVisible: true, - size: 150, - }, - ], - sorts: [ - { - universalIdentifier: 'c0c54264-8de9-418b-b91a-cc4a2b94c539', - fieldMetadataUniversalIdentifier: - CONTRIBUTOR_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - direction: ViewSortDirection.DESC, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/command-menu-items/fetch-issues.command-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/command-menu-items/fetch-issues.command-menu-item.ts deleted file mode 100644 index 2eb31aa011..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/command-menu-items/fetch-issues.command-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineCommandMenuItem, objectMetadataItem } from 'twenty-sdk/define'; - -export default defineCommandMenuItem({ - universalIdentifier: 'c34f56aa-ff65-43a4-9db2-774945dbcc53', - frontComponentUniversalIdentifier: '9430e4fc-9ecb-428d-9bde-2babeb1f452f', - label: 'Fetch Issues', - icon: 'IconBug', - isPinned: false, - conditionalAvailabilityExpression: - objectMetadataItem.nameSingular === 'issue', -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/fields/author-on-issue.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/fields/author-on-issue.field.ts deleted file mode 100644 index 9df18c1ba1..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/fields/author-on-issue.field.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { ISSUE_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/objects/issue.object'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { AUTHORED_ISSUES_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/fields/authored-issues-on-contributor.field'; - -export const ISSUE_AUTHOR_FIELD_UNIVERSAL_IDENTIFIER = - '5ff7ab7f-bcfa-4e7a-9a33-0860ec692736'; - -export default defineField({ - universalIdentifier: ISSUE_AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'author', - label: 'Author', - icon: 'IconUser', - relationTargetObjectMetadataUniversalIdentifier: - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - AUTHORED_ISSUES_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'authorId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/fields/project-items-on-issue.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/fields/project-items-on-issue.field.ts deleted file mode 100644 index b7713df535..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/fields/project-items-on-issue.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { ISSUE_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/objects/issue.object'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { LINKED_ISSUE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/fields/linked-issue-on-project-item.field'; - -export const PROJECT_ITEMS_ON_ISSUE_FIELD_UNIVERSAL_IDENTIFIER = - '8b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e'; - -export default defineField({ - universalIdentifier: PROJECT_ITEMS_ON_ISSUE_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'projectItems', - label: 'Project Items', - icon: 'IconLayoutKanban', - relationTargetObjectMetadataUniversalIdentifier: - PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - LINKED_ISSUE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/front-components/fetch-issues.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/issue/front-components/fetch-issues.front-component.tsx deleted file mode 100644 index afab8218fe..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/front-components/fetch-issues.front-component.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useEffect, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - enqueueSnackbar, - unmountFrontComponent, - updateProgress, -} from 'twenty-sdk/front-component'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; -import { retry } from 'src/modules/shared/retry'; - -type CountResponse = { - totalPages: number; - repos: Array<{ owner: string; repo: string; totalCount: number; pages: number }>; -}; - -type FetchPageResponse = { - issueCount: number; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type SyncStatus = 'syncing' | 'done' | 'error'; - -const FetchIssues = () => { - const [status, setStatus] = useState('syncing'); - - useEffect(() => { - const run = async () => { - try { - const counts = (await callAppRoute( - '/github/count-issues', - {}, - )) as CountResponse; - - if (counts.repos.length === 0) { - throw new Error( - 'No repos resolved. Set GITHUB_REPOS in the application variables.', - ); - } - - const totalPages = Math.max(counts.totalPages, 1); - let pagesProcessed = 0; - let totalIssues = 0; - - for (const { owner, repo } of counts.repos) { - let cursor: string | null = null; - let hasMore = true; - - while (hasMore) { - const cursorTag = cursor ? `@${cursor.slice(0, 8)}` : ''; - const data = (await retry( - `fetch-issues ${owner}/${repo}${cursorTag}`, - () => - callAppRoute('/github/fetch-issues', { - owner, - repo, - cursor, - }), - )) as FetchPageResponse; - - totalIssues += data.issueCount; - hasMore = data.hasMore && data.issueCount > 0; - cursor = data.endCursor; - pagesProcessed++; - - updateProgress(Math.min(Math.round((pagesProcessed / totalPages) * 100), 99)); - } - } - - updateProgress(100); - enqueueSnackbar({ - message: `Fetched ${totalIssues} issues`, - variant: 'success', - }); - setStatus('done'); - } catch (err) { - const message = - err instanceof Error ? err.message : 'Failed to fetch issues'; - enqueueSnackbar({ message, variant: 'error' }); - setStatus('error'); - } finally { - unmountFrontComponent(); - } - }; - run(); - }, []); - - if (status === 'syncing') return
Fetching issues...
; - if (status === 'error') return
Failed to fetch issues.
; - return
Done
; -}; - -export default defineFrontComponent({ - universalIdentifier: '9430e4fc-9ecb-428d-9bde-2babeb1f452f', - name: 'Fetch Issues', - description: 'Fetches issues from GitHub repos', - isHeadless: true, - component: FetchIssues, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/github/count-issues.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/github/count-issues.ts deleted file mode 100644 index b78777210c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/github/count-issues.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { githubGraphql } from 'src/modules/github/connector/github-client'; - -const QUERY = ` -query($owner: String!, $name: String!) { - repository(owner: $owner, name: $name) { - issues(states: [OPEN, CLOSED]) { - totalCount - } - } -}`; - -type Response = { - repository: { issues: { totalCount: number } } | null; -}; - -export async function countIssues( - owner: string, - name: string, -): Promise { - try { - const data = await githubGraphql(QUERY, { owner, name }); - return data.repository?.issues.totalCount ?? 0; - } catch { - return 0; - } -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/github/fetch-issues.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/github/fetch-issues.ts deleted file mode 100644 index 2b9df95a78..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/github/fetch-issues.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - EMPTY_PAGE, - type GithubPage, - githubGraphql, -} from 'src/modules/github/connector/github-client'; - -const QUERY = ` -query($owner: String!, $name: String!, $cursor: String) { - repository(owner: $owner, name: $name) { - issues(first: 100, states: [OPEN, CLOSED], orderBy: {field: CREATED_AT, direction: DESC}, after: $cursor) { - totalCount - pageInfo { hasNextPage endCursor } - nodes { - number - title - url - state - createdAt - closedAt - author { login avatarUrl ... on User { databaseId } } - labels(first: 50) { nodes { name } } - } - } - } -}`; - -export type GqlIssue = { - number: number; - title: string; - url: string; - state: 'OPEN' | 'CLOSED'; - createdAt: string; - closedAt: string | null; - author: { login: string; avatarUrl?: string | null; databaseId?: number } | null; - labels: { nodes: Array<{ name: string }> }; -}; - -type Response = { - repository: { issues: GithubPage } | null; -}; - -export async function fetchIssues( - owner: string, - name: string, - cursor: string | null = null, -): Promise<{ - issues: GqlIssue[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}> { - try { - const data = await githubGraphql(QUERY, { owner, name, cursor }); - const conn = data.repository?.issues; - if (!conn) return { issues: [], ...EMPTY_PAGE }; - - return { - issues: conn.nodes, - totalCount: conn.totalCount, - hasMore: conn.pageInfo.hasNextPage, - endCursor: conn.pageInfo.endCursor, - }; - } catch { - return { issues: [], ...EMPTY_PAGE }; - } -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/mutations/batch-upsert.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/mutations/batch-upsert.ts deleted file mode 100644 index 2a2995e0f2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/mutations/batch-upsert.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { chunkedBatchCreate } from 'src/modules/shared/twenty-client'; -import type { LinksFieldValue } from 'src/modules/shared/types'; -import type { IssueRow } from 'src/modules/github/issue/types/issue-row'; - -export async function batchUpsertIssues( - items: Array<{ - title: string; - githubNumber: number; - uniqueIdentifier: string; - githubUrl: LinksFieldValue; - state: string; - labels: string[]; - githubCreatedAt: string | null; - closedAt: string | null; - repo: string; - authorId: string | null; - }>, -): Promise { - return chunkedBatchCreate('createIssues', items, { - id: true, - githubNumber: true, - uniqueIdentifier: true, - title: true, - state: true, - repo: true, - }) as Promise; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/queries/find-by-number-and-repo.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/queries/find-by-number-and-repo.ts deleted file mode 100644 index 5f7d7681b9..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/graphql/queries/find-by-number-and-repo.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getClient } from 'src/modules/shared/twenty-client'; -import type { IssueRow } from 'src/modules/github/issue/types/issue-row'; - -export async function findIssueByNumberAndRepo( - githubNumber: number, - repo: string, -): Promise { - const client = getClient(); - const res = await client.query({ - issues: { - __args: { - filter: { - and: [ - { githubNumber: { eq: githubNumber } }, - { repo: { eq: repo } }, - ], - }, - first: 1, - }, - edges: { - node: { - id: true, - title: true, - githubNumber: true, - uniqueIdentifier: true, - githubUrl: { primaryLinkLabel: true, primaryLinkUrl: true }, - state: true, - labels: true, - githubCreatedAt: true, - closedAt: true, - repo: true, - authorId: true, - }, - }, - }, - }); - - const node = res.issues?.edges?.[0]?.node; - return (node as IssueRow | undefined) ?? null; -} 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 deleted file mode 100644 index 90d264aae2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/count-issues.logic-function.ts +++ /dev/null @@ -1,25 +0,0 @@ -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'; - -type CountIssuesPayload = { - repos?: string[]; -}; - -const handler = async (event: RoutePayload) => - countAcrossRepos(event.body?.repos, countIssues, 'count-issues'); - -export default defineLogicFunction({ - universalIdentifier: 'd8cc32bf-6be9-44fc-920a-8bba510f045f', - name: 'count-issues', - description: - 'Counts total issue pages across configured repos using GraphQL totalCount', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/github/count-issues', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 306cee67ea..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/logic-functions/fetch-issues.logic-function.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { defineLogicFunction } from 'twenty-sdk/define'; -import { type RoutePayload } from 'twenty-sdk/logic-function'; -import { - fetchIssues, - type GqlIssue, -} from 'src/modules/github/issue/graphql/github/fetch-issues'; -import { batchUpsertContributors } from 'src/modules/github/contributor/graphql/mutations/batch-upsert'; -import { batchUpsertIssues } from 'src/modules/github/issue/graphql/mutations/batch-upsert'; -import { dedupeContributors } from 'src/modules/github/contributor/normalizers'; -import { issueFromGraphql } from 'src/modules/github/issue/normalizers'; -import { timed } from 'src/modules/shared/timing'; -import { isFixtureAllowed } from 'src/modules/shared/fixtures'; - -export type FetchIssuesFixturePage = { - issues: GqlIssue[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type FetchIssuesPayload = { - owner: string; - repo: string; - cursor?: string | null; - fixturePage?: FetchIssuesFixturePage; -}; - -const handler = async (event: RoutePayload) => { - const handlerStart = Date.now(); - const { owner, repo, cursor = null, fixturePage } = event.body ?? {}; - if (!owner || !repo) { - return { error: 'owner and repo are required' }; - } - - const tag = `${owner}/${repo}${cursor ? `@${cursor.slice(0, 8)}` : ''}`; - console.log(`[fetch-issues] start ${tag}${fixturePage ? ' (fixture)' : ''}`); - - const result = - fixturePage && isFixtureAllowed() - ? fixturePage - : await timed(`fetch-issues:github ${tag}`, () => - fetchIssues(owner, repo, cursor), - ); - const { issues, totalCount, hasMore, endCursor } = result; - - if (issues.length === 0) { - console.log(`[fetch-issues] empty page for ${tag}`); - return { issueCount: 0, totalCount, hasMore: false, endCursor: null }; - } - - const fullRepo = `${owner}/${repo}`; - - const contributorInputs = dedupeContributors(issues.map((i) => i.author)); - const contributors = await timed( - `fetch-issues:upsertContributors ${tag} (${contributorInputs.length})`, - () => batchUpsertContributors(contributorInputs), - ); - - const idByLogin = new Map(); - for (const c of contributors) { - if (c.ghLogin) idByLogin.set(c.ghLogin, c.id); - } - - const issueData = issues.map((issue) => ({ - ...issueFromGraphql(issue, fullRepo), - authorId: issue.author ? (idByLogin.get(issue.author.login) ?? null) : null, - })); - - await timed(`fetch-issues:upsertIssues ${tag} (${issueData.length})`, () => - batchUpsertIssues(issueData), - ); - - const totalMs = Date.now() - handlerStart; - console.log( - `[fetch-issues] done ${tag} in ${totalMs}ms (issues=${issues.length}, hasMore=${hasMore})`, - ); - - return { issueCount: issues.length, totalCount, hasMore, endCursor }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e', - name: 'fetch-issues', - description: - 'Fetches one page of issues via GitHub GraphQL API and batch upserts them', - timeoutSeconds: 300, - handler, - httpRouteTriggerSettings: { - path: '/github/fetch-issues', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/navigation-menu-items/issues.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/navigation-menu-items/issues.navigation-menu-item.ts deleted file mode 100644 index ed4c89df6c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/navigation-menu-items/issues.navigation-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { ISSUE_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/objects/issue.object'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; - -export default defineNavigationMenuItem({ - universalIdentifier: '7c4f8e1a-3b9d-4f2e-8a6c-1d5b7e9f3a2c', - position: 1, - type: NavigationMenuItemType.OBJECT, - targetObjectUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/normalizers.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/normalizers.ts deleted file mode 100644 index b4045d9b16..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/normalizers.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; -import { toLinksField } from 'src/modules/shared/types'; -import type { GitHubIssue } from 'src/modules/github/issue/types/github-issue'; -import type { GqlIssue } from 'src/modules/github/issue/graphql/github/fetch-issues'; - -export type IssueState = 'OPEN' | 'CLOSED'; - -export type IssueCanonical = { - title: string; - githubNumber: number; - uniqueIdentifier: string; - githubUrl: LinksFieldValue; - state: IssueState; - labels: string[]; - githubCreatedAt: string | null; - closedAt: string | null; - repo: string; -}; - -export function deriveIssueState(state: string): IssueState { - return state.toUpperCase() === 'CLOSED' ? 'CLOSED' : 'OPEN'; -} - -export function buildIssueUniqueIdentifier( - repoFullName: string, - number: number, -): string { - return `${repoFullName}#${number}`; -} - -export function issueFromWebhook( - issue: GitHubIssue, - repoFullName: string, -): IssueCanonical { - return { - title: issue.title, - githubNumber: issue.number, - uniqueIdentifier: buildIssueUniqueIdentifier(repoFullName, issue.number), - githubUrl: toLinksField(issue.html_url, issue.title), - state: deriveIssueState(issue.state), - labels: issue.labels.map((l) => l.name), - githubCreatedAt: issue.created_at, - closedAt: issue.closed_at, - repo: repoFullName, - }; -} - -export function issueFromGraphql( - issue: GqlIssue, - repoFullName: string, -): IssueCanonical { - return { - title: issue.title, - githubNumber: issue.number, - uniqueIdentifier: buildIssueUniqueIdentifier(repoFullName, issue.number), - githubUrl: toLinksField(issue.url, issue.title), - state: deriveIssueState(issue.state), - labels: issue.labels.nodes.map((l) => l.name), - githubCreatedAt: issue.createdAt, - closedAt: issue.closedAt, - repo: repoFullName, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/objects/issue.object.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/objects/issue.object.ts deleted file mode 100644 index 64fa230202..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/objects/issue.object.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { defineObject, FieldType } from 'twenty-sdk/define'; - -export const ISSUE_UNIVERSAL_IDENTIFIER = - '3a4b5c6d-7e8f-4a9b-8c0d-1e2f3a4b5c6d'; - -export const ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER = - '4b5c6d7e-8f9a-4b0c-9d1e-2f3a4b5c6d7e'; - -export const ISSUE_GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER = - '5c6d7e8f-9a0b-4c1d-ae2f-3a4b5c6d7e8f'; - -export const ISSUE_GITHUB_URL_FIELD_UNIVERSAL_IDENTIFIER = - '6d7e8f9a-0b1c-4d2e-bf3a-4b5c6d7e8f9a'; - -export const ISSUE_LABELS_FIELD_UNIVERSAL_IDENTIFIER = - 'dfd35d92-f4a3-44c5-b758-494c0882fcc8'; - -export const ISSUE_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '76364df9-6f5c-477d-bd99-97b6738df3bf'; - -export const ISSUE_CLOSED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '48ac555b-73b7-4ce9-a264-251c8e06b4e9'; - -export const ISSUE_REPO_FIELD_UNIVERSAL_IDENTIFIER = - '79c41cdc-912a-4e4d-b9de-3f2421bcb2fb'; - -export const ISSUE_UNIQUE_IDENTIFIER_FIELD_UNIVERSAL_IDENTIFIER = - '0ca04207-4d74-4298-8238-59cdf685862b'; - -enum IssueState { - OPEN = 'OPEN', - CLOSED = 'CLOSED', -} - -export const ISSUE_STATE_FIELD_UNIVERSAL_IDENTIFIER = - '1c2d3e4f-5a6b-4c7d-a48e-9f0a1b2c3d4e'; - -export const ISSUE_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '440e9ba8-20d1-5843-91ef-3b7b699a59f4'; - -export default defineObject({ - universalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - nameSingular: 'issue', - namePlural: 'issues', - labelSingular: 'Issue', - labelPlural: 'Issues', - icon: 'IconBug', - labelIdentifierFieldMetadataUniversalIdentifier: - ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - fields: [ - { - universalIdentifier: ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'title', - type: FieldType.TEXT, - label: 'Title', - icon: 'IconTextCaption', - }, - { - universalIdentifier: ISSUE_GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubNumber', - type: FieldType.NUMBER, - label: 'Issue Number', - icon: 'IconHash', - }, - { - universalIdentifier: ISSUE_GITHUB_URL_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubUrl', - type: FieldType.LINKS, - label: 'GitHub URL', - icon: 'IconLink', - }, - { - universalIdentifier: ISSUE_STATE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'state', - type: FieldType.SELECT, - label: 'State', - icon: 'IconCircleDot', - options: [ - { - value: IssueState.OPEN, - label: 'Open', - position: 0, - color: 'green', - }, - { - value: IssueState.CLOSED, - label: 'Closed', - position: 1, - color: 'red', - }, - ], - }, - { - universalIdentifier: ISSUE_LABELS_FIELD_UNIVERSAL_IDENTIFIER, - name: 'labels', - type: FieldType.ARRAY, - label: 'Labels', - icon: 'IconTag', - }, - { - universalIdentifier: ISSUE_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubCreatedAt', - type: FieldType.DATE_TIME, - label: 'GitHub Created At', - icon: 'IconCalendarPlus', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: ISSUE_CLOSED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'closedAt', - type: FieldType.DATE_TIME, - label: 'Closed At', - icon: 'IconCalendarOff', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: ISSUE_REPO_FIELD_UNIVERSAL_IDENTIFIER, - name: 'repo', - type: FieldType.TEXT, - label: 'Repository', - icon: 'IconFolder', - }, - { - universalIdentifier: ISSUE_UNIQUE_IDENTIFIER_FIELD_UNIVERSAL_IDENTIFIER, - name: 'uniqueIdentifier', - type: FieldType.TEXT, - label: 'Unique Identifier', - icon: 'IconFingerprint', - isUnique: true, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/types/github-issue.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/types/github-issue.ts deleted file mode 100644 index 57619c0cbb..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/types/github-issue.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { GitHubUser } from 'src/modules/github/connector/github-user'; - -export type GitHubIssue = { - number: number; - title: string; - html_url: string; - state: 'open' | 'closed'; - labels: Array<{ name: string }>; - created_at: string; - closed_at: string | null; - user: GitHubUser; - pull_request?: unknown; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/types/issue-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/types/issue-row.ts deleted file mode 100644 index 32df42ca55..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/types/issue-row.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; - -export type IssueRow = { - id: string; - title?: string | null; - githubNumber?: number | null; - uniqueIdentifier?: string | null; - githubUrl?: LinksFieldValue | null; - state?: string | null; - labels?: string[] | null; - githubCreatedAt?: string | null; - closedAt?: string | null; - repo?: string | null; - authorId?: string | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/issue/views/all-issues.view.ts b/packages/twenty-apps/community/github-connector/src/modules/github/issue/views/all-issues.view.ts deleted file mode 100644 index ea429bda16..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/issue/views/all-issues.view.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { - defineView, - ViewKey, - ViewSortDirection, - ViewType, -} from 'twenty-sdk/define'; -import { - ISSUE_UNIVERSAL_IDENTIFIER, - ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_STATE_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_LABELS_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_REPO_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/issue/objects/issue.object'; -import { ISSUE_AUTHOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/fields/author-on-issue.field'; - -export const ALL_ISSUES_VIEW_UNIVERSAL_IDENTIFIER = - 'b2c3d4e5-1111-4f6a-b7c8-d9e0f1a2b3c4'; - -export default defineView({ - universalIdentifier: ALL_ISSUES_VIEW_UNIVERSAL_IDENTIFIER, - name: 'All Issues', - objectUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - type: ViewType.TABLE, - icon: 'IconBug', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: '5b115bd1-489c-44e5-ac1b-7d508e06f165', - fieldMetadataUniversalIdentifier: - ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 300, - }, - { - universalIdentifier: '77ac1882-3a13-43e3-822e-c9f5f7d83a67', - fieldMetadataUniversalIdentifier: - ISSUE_GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER, - position: 1, - isVisible: true, - size: 100, - }, - { - universalIdentifier: '4f0d17f2-c3a6-4431-a928-3812273592e2', - fieldMetadataUniversalIdentifier: - ISSUE_STATE_FIELD_UNIVERSAL_IDENTIFIER, - position: 2, - isVisible: true, - size: 100, - }, - { - universalIdentifier: '01673163-ccfa-4370-a1c3-48c33b6c4aa3', - fieldMetadataUniversalIdentifier: - ISSUE_LABELS_FIELD_UNIVERSAL_IDENTIFIER, - position: 3, - isVisible: true, - size: 200, - }, - { - universalIdentifier: '6d932c97-8f92-4d93-968f-29ca34b27ac0', - fieldMetadataUniversalIdentifier: - ISSUE_REPO_FIELD_UNIVERSAL_IDENTIFIER, - position: 4, - isVisible: true, - size: 180, - }, - { - universalIdentifier: 'dc293fd6-747c-4291-9426-5d7b53234936', - fieldMetadataUniversalIdentifier: - ISSUE_AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - position: 5, - isVisible: true, - size: 150, - }, - ], - sorts: [ - { - universalIdentifier: '8e972333-b919-4c1b-ad7b-4fab8f018253', - fieldMetadataUniversalIdentifier: - ISSUE_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - direction: ViewSortDirection.DESC, - }, - ], -}); 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 deleted file mode 100644 index fa2701169e..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/logic-functions/handle-webhook.logic-function.ts +++ /dev/null @@ -1,266 +0,0 @@ -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'; -import { - getRawBodyForSignature, - verifyGitHubSignature, -} from 'src/modules/github/connector/webhook-signature'; -import { batchUpsertContributors } from 'src/modules/github/contributor/graphql/mutations/batch-upsert'; -import { batchUpsertPullRequests } from 'src/modules/github/pull-request/graphql/mutations/batch-upsert'; -import { batchUpsertReviewEvents } from 'src/modules/github/pull-request-review-event/graphql/mutations/batch-upsert'; -import { batchUpsertConsolidatedReviews } from 'src/modules/github/pull-request-review/graphql/mutations/batch-upsert'; -import { findReviewEventsForPair } from 'src/modules/github/pull-request-review-event/graphql/queries/find-events-for-pair'; -import { buildConsolidatedRow } from 'src/modules/github/pull-request-review/utils/build-consolidated-row'; -import type { ReviewEventState } from 'src/modules/github/pull-request-review/utils/consolidate-reviews'; -import { batchUpsertIssues } from 'src/modules/github/issue/graphql/mutations/batch-upsert'; -import { batchUpsertProjectItems } from 'src/modules/github/project-item/graphql/mutations/batch-upsert'; -import { dedupeContributors } from 'src/modules/github/contributor/normalizers'; -import { pullRequestFromWebhook } from 'src/modules/github/pull-request/normalizers'; -import { reviewEventFromWebhook } from 'src/modules/github/pull-request-review-event/normalizers'; -import { issueFromWebhook } from 'src/modules/github/issue/normalizers'; -import { projectItemFromGraphql } from 'src/modules/github/project-item/normalizers'; - -async function upsertContributorsByLogin( - users: Array<{ login: string; id?: number } | null | undefined>, -): Promise> { - const inputs = dedupeContributors(users); - if (inputs.length === 0) return new Map(); - const rows = await batchUpsertContributors(inputs); - const map = new Map(); - for (const r of rows) { - if (r.ghLogin) map.set(r.ghLogin, r.id); - } - return map; -} - -async function handlePullRequestEvent(payload: GitHubWebhookPayload) { - const pr = payload.pull_request; - const repository = payload.repository; - if (!pr || !repository) { - return { skipped: true, reason: 'missing pull_request or repository' }; - } - - 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 canonical = pullRequestFromWebhook(pr, repository.full_name); - - const [record] = await batchUpsertPullRequests([ - { ...canonical, authorId, mergerId }, - ]); - - return { - processed: true, - pullRequestId: record?.id, - action: payload.action, - state: canonical.state, - }; -} - -async function handlePullRequestReviewEvent(payload: GitHubWebhookPayload) { - const review = payload.review; - const pr = payload.pull_request; - const repository = payload.repository; - if (!review || !pr || !repository) { - return { - skipped: true, - reason: 'missing review, pull_request, or repository', - }; - } - - const idByLogin = await upsertContributorsByLogin([ - pr.user, - pr.merged_by, - review.user, - ]); - - const prCanonical = pullRequestFromWebhook(pr, repository.full_name); - const [prRecord] = await batchUpsertPullRequests([ - { - ...prCanonical, - authorId: idByLogin.get(pr.user.login) ?? null, - mergerId: pr.merged_by - ? (idByLogin.get(pr.merged_by.login) ?? null) - : null, - }, - ]); - - const reviewCanonical = reviewEventFromWebhook(review); - const reviewerId = idByLogin.get(review.user.login) ?? null; - const pullRequestId = prRecord?.id ?? ''; - - const [eventRecord] = await batchUpsertReviewEvents([ - { - ...reviewCanonical, - reviewerId, - pullRequestId, - }, - ]); - - let consolidatedId: string | undefined; - if (pullRequestId) { - const events = await findReviewEventsForPair(pullRequestId, reviewerId); - if (events.length > 0) { - const consolidatedRow = buildConsolidatedRow({ - pullRequestId, - reviewerId, - prNumber: pr.number ?? null, - reviewerLogin: review.user.login, - events: events.map((e) => ({ - state: e.state as ReviewEventState, - submittedAt: e.submittedAt, - })), - }); - const [consolidatedRecord] = await batchUpsertConsolidatedReviews([ - consolidatedRow, - ]); - consolidatedId = consolidatedRecord?.id; - - if (consolidatedId && eventRecord?.id) { - await batchUpsertReviewEvents([ - { - ...reviewCanonical, - reviewerId, - pullRequestId, - reviewId: consolidatedId, - }, - ]); - } - } - } - - return { - processed: true, - reviewEventId: eventRecord?.id, - reviewId: consolidatedId, - pullRequestId: prRecord?.id, - action: payload.action, - }; -} - -async function handleIssueEvent(payload: GitHubWebhookPayload) { - const issue = payload.issue; - const repository = payload.repository; - if (!issue || !repository) { - return { skipped: true, reason: 'missing issue or repository' }; - } - - const idByLogin = await upsertContributorsByLogin([issue.user]); - const canonical = issueFromWebhook(issue, repository.full_name); - - const [record] = await batchUpsertIssues([ - { ...canonical, authorId: idByLogin.get(issue.user.login) ?? null }, - ]); - - return { - processed: true, - issueId: record?.id, - action: payload.action, - }; -} - -async function handleProjectV2ItemEvent( - payload: GitHubWebhookPayload, - testProjectItem?: ProjectV2Item, -) { - const item = payload.projects_v2_item; - if (!item?.node_id) { - return { skipped: true, reason: 'missing projects_v2_item.node_id' }; - } - - if (payload.action === 'deleted') { - console.log( - `[handle-webhook] projects_v2_item delete skipped (nodeId=${item.node_id})`, - ); - return { skipped: true, reason: 'delete not implemented' }; - } - - const node = - testProjectItem ?? (await fetchProjectItemByNodeId(item.node_id)); - if (!node) { - return { skipped: true, reason: 'project item not found on GitHub' }; - } - - const canonical = await projectItemFromGraphql(node); - const [record] = await batchUpsertProjectItems([canonical]); - - return { - processed: true, - projectItemId: record?.id, - action: payload.action, - }; -} - -const handler = async ( - event: RoutePayload< - GitHubWebhookPayload & { __test_projectItem?: ProjectV2Item } - >, -) => { - const secret = process.env.GITHUB_WEBHOOK_SECRET; - if (secret) { - const signatureHeader = - event.headers?.['x-hub-signature-256'] ?? - event.headers?.['X-Hub-Signature-256']; - const rawBody = getRawBodyForSignature(event); - const verification = verifyGitHubSignature({ - rawBody, - signatureHeader, - secret, - }); - if (!verification.ok) { - const delivery = - event.headers?.['x-github-delivery'] ?? - event.headers?.['X-GitHub-Delivery']; - console.warn( - `[handle-webhook] signature verification failed (${verification.reason}) delivery=${delivery ?? 'unknown'}`, - ); - return { error: 'invalid signature', reason: verification.reason }; - } - } else { - console.warn( - '[handle-webhook] GITHUB_WEBHOOK_SECRET is not set; skipping signature verification (insecure)', - ); - } - - const payload = event.body; - if (!payload) return { error: 'empty body' }; - - if (payload.pull_request && payload.review) { - return handlePullRequestReviewEvent(payload); - } - if (payload.pull_request) { - return handlePullRequestEvent(payload); - } - if (payload.issue) { - return handleIssueEvent(payload); - } - if (payload.projects_v2_item) { - return handleProjectV2ItemEvent(payload, payload.__test_projectItem); - } - - return { skipped: true, reason: 'unhandled event' }; -}; - -export default defineLogicFunction({ - universalIdentifier: '22b199b3-2851-4a4f-99fd-4e79c188fe7d', - name: 'handle-github-webhook', - description: - 'Receives GitHub webhook events for PRs, reviews, issues, and project items; idempotent upserts via shared batch helpers', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/github/webhook', - httpMethod: 'POST', - isAuthRequired: false, - forwardedRequestHeaders: [ - 'x-hub-signature-256', - 'x-github-event', - 'x-github-delivery', - ], - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/contributors.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/contributors.navigation-menu-item.ts deleted file mode 100644 index 437638a82c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/contributors.navigation-menu-item.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - defineNavigationMenuItem, - NavigationMenuItemType, -} from 'twenty-sdk/define'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; - -export default defineNavigationMenuItem({ - universalIdentifier: 'd1c2b3a4-9e8f-4d3c-b2a1-0f9e8d7c6b5a', - position: 3, - type: NavigationMenuItemType.OBJECT, - targetObjectUniversalIdentifier: CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/github-dashboard.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/github-dashboard.navigation-menu-item.ts deleted file mode 100644 index 9a2db40645..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/github-dashboard.navigation-menu-item.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - defineNavigationMenuItem, - NavigationMenuItemType, -} from 'twenty-sdk/define'; -import { GITHUB_DASHBOARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/page-layouts/github-dashboard.page-layout'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; - -export default defineNavigationMenuItem({ - universalIdentifier: 'e8f9a1b2-c3d4-4567-89ab-cdef01234567', - name: 'GitHub Dashboard', - icon: 'IconBrandGithub', - position: 6, - type: NavigationMenuItemType.PAGE_LAYOUT, - pageLayoutUniversalIdentifier: - GITHUB_DASHBOARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/github-folder.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/github-folder.navigation-menu-item.ts deleted file mode 100644 index 16658a3eec..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/navigation-menu-items/github-folder.navigation-menu-item.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; - -export const GITHUB_FOLDER_UNIVERSAL_IDENTIFIER = - 'a4f7d8b1-2c93-4e6f-8b1a-9d3e5c7f2a48'; - -export default defineNavigationMenuItem({ - universalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, - name: 'GitHub', - icon: 'IconBrandGithub', - color: 'gray', - position: 0, - type: NavigationMenuItemType.FOLDER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/page-layouts/github-dashboard.page-layout.ts b/packages/twenty-apps/community/github-connector/src/modules/github/page-layouts/github-dashboard.page-layout.ts deleted file mode 100644 index 70831ca6e5..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/page-layouts/github-dashboard.page-layout.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { - AggregateOperations, - definePageLayout, - ObjectRecordGroupByDateGranularity, - PageLayoutTabLayoutMode, -} from 'twenty-sdk/define'; -import { TOP_PR_AUTHORS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/front-components/top-pr-authors.front-component'; -import { TOP_REVIEWERS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/front-components/top-reviewers.front-component'; -import { - ISSUE_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_STATE_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - ISSUE_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/issue/objects/issue.object'; -import { - MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER, - PR_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - PR_STATE_FIELD_UNIVERSAL_IDENTIFIER, - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - PULL_REQUEST_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/pull-request/objects/pull-request.object'; -import { - PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; - -export const GITHUB_DASHBOARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = - 'a8d2f1c4-7b69-4e3a-8c5d-9f6b1a3e7c2d'; - -const THIS_WEEK_RELATIVE = 'THIS_1_WEEK;;UTC;;SUNDAY;;'; - -const COMMON = { - timezone: 'UTC', - firstDayOfTheWeek: 0, -}; - -const COL_1 = { column: 0, columnSpan: 3 } as const; -const COL_2 = { column: 3, columnSpan: 5 } as const; -const COL_3 = { column: 8, columnSpan: 4 } as const; - -export default definePageLayout({ - universalIdentifier: GITHUB_DASHBOARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, - name: 'GitHub Dashboard', - type: 'STANDALONE_PAGE', - tabs: [ - { - universalIdentifier: 'b3e9c2a1-4d68-4f7b-9c25-1e3a5b7c9d11', - title: 'GitHub Dashboard', - position: 0, - icon: 'IconBrandGithub', - layoutMode: PageLayoutTabLayoutMode.GRID, - widgets: [ - { - universalIdentifier: 'f1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b1', - title: 'PRs Merged This Week', - type: 'GRAPH', - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 0, ...COL_1, rowSpan: 3 }, - configuration: { - configurationType: 'AGGREGATE_CHART', - aggregateFieldMetadataUniversalIdentifier: - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - label: 'PRs Merged', - color: 'green', - displayDataLabel: false, - ...COMMON, - filter: { - recordFilters: [ - { - fieldMetadataUniversalIdentifier: - MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER, - operand: 'IS_RELATIVE', - value: THIS_WEEK_RELATIVE, - type: 'DATE_TIME', - }, - ], - }, - }, - }, - { - universalIdentifier: 'f1a2b3c4-d5e6-4789-a0b1-c2d3e4f5a6b2', - title: 'PR Reviews This Week', - type: 'GRAPH', - objectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 3, ...COL_1, rowSpan: 3 }, - configuration: { - configurationType: 'AGGREGATE_CHART', - aggregateFieldMetadataUniversalIdentifier: - REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - label: 'Reviews', - color: 'blue', - displayDataLabel: false, - ...COMMON, - filter: { - recordFilters: [ - { - fieldMetadataUniversalIdentifier: - REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - operand: 'IS_RELATIVE', - value: THIS_WEEK_RELATIVE, - type: 'DATE_TIME', - }, - ], - }, - }, - }, - { - universalIdentifier: '8d4e9d09-29b0-4925-8fcc-aedf5bcecb37', - title: 'PRs Opened This Week', - type: 'GRAPH', - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 6, ...COL_1, rowSpan: 3 }, - configuration: { - configurationType: 'AGGREGATE_CHART', - aggregateFieldMetadataUniversalIdentifier: - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - label: 'PRs Opened', - color: 'purple', - displayDataLabel: false, - ...COMMON, - filter: { - recordFilters: [ - { - fieldMetadataUniversalIdentifier: - PR_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - operand: 'IS_RELATIVE', - value: THIS_WEEK_RELATIVE, - type: 'DATE_TIME', - }, - ], - }, - }, - }, - { - universalIdentifier: '3c321867-0227-4535-81db-c64a6f673d86', - title: 'Issues Opened This Week', - type: 'GRAPH', - objectUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 9, ...COL_1, rowSpan: 3 }, - configuration: { - configurationType: 'AGGREGATE_CHART', - aggregateFieldMetadataUniversalIdentifier: - ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - label: 'Issues Opened', - color: 'orange', - displayDataLabel: false, - ...COMMON, - filter: { - recordFilters: [ - { - fieldMetadataUniversalIdentifier: - ISSUE_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - operand: 'IS_RELATIVE', - value: THIS_WEEK_RELATIVE, - type: 'DATE_TIME', - }, - ], - }, - }, - }, - { - universalIdentifier: '704209be-9174-43a8-ae51-6b11fff20e22', - title: 'PRs by State', - type: 'GRAPH', - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 12, ...COL_1, rowSpan: 6 }, - configuration: { - configurationType: 'PIE_CHART', - aggregateFieldMetadataUniversalIdentifier: - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - groupByFieldMetadataUniversalIdentifier: - PR_STATE_FIELD_UNIVERSAL_IDENTIFIER, - displayLegend: true, - showCenterMetric: true, - hideEmptyCategory: true, - ...COMMON, - }, - }, - { - universalIdentifier: 'c1f2a3b4-d5e6-4789-a0b1-c2d3e4f5a6b7', - title: 'PRs Merged per Week', - type: 'GRAPH', - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 0, ...COL_2, rowSpan: 6 }, - configuration: { - configurationType: 'BAR_CHART', - aggregateFieldMetadataUniversalIdentifier: - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - primaryAxisGroupByFieldMetadataUniversalIdentifier: - MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER, - primaryAxisDateGranularity: - ObjectRecordGroupByDateGranularity.WEEK, - displayDataLabel: false, - displayLegend: false, - color: 'green', - layout: 'VERTICAL', - omitNullValues: true, - ...COMMON, - }, - }, - { - universalIdentifier: 'd2e3f4a5-b6c7-4890-a1b2-c3d4e5f6a7b8', - title: 'PR Reviews per Week', - type: 'GRAPH', - objectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 6, ...COL_2, rowSpan: 6 }, - configuration: { - configurationType: 'BAR_CHART', - aggregateFieldMetadataUniversalIdentifier: - REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - primaryAxisGroupByFieldMetadataUniversalIdentifier: - REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - primaryAxisDateGranularity: - ObjectRecordGroupByDateGranularity.WEEK, - displayDataLabel: false, - displayLegend: false, - color: 'blue', - layout: 'VERTICAL', - omitNullValues: true, - ...COMMON, - }, - }, - { - universalIdentifier: '6d03cbd1-ee03-4bf4-a3ac-143d03fa7fd9', - title: 'Issues per Week', - type: 'GRAPH', - objectUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - gridPosition: { row: 12, ...COL_2, rowSpan: 6 }, - configuration: { - configurationType: 'BAR_CHART', - aggregateFieldMetadataUniversalIdentifier: - ISSUE_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - aggregateOperation: AggregateOperations.COUNT, - primaryAxisGroupByFieldMetadataUniversalIdentifier: - ISSUE_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - primaryAxisDateGranularity: - ObjectRecordGroupByDateGranularity.WEEK, - secondaryAxisGroupByFieldMetadataUniversalIdentifier: - ISSUE_STATE_FIELD_UNIVERSAL_IDENTIFIER, - displayDataLabel: false, - displayLegend: true, - color: 'orange', - layout: 'VERTICAL', - omitNullValues: true, - ...COMMON, - }, - }, - { - universalIdentifier: '7b3e9c4a-1d52-4f8b-ac76-3e5b8d2f1a9c', - title: 'Top PR Authors', - type: 'FRONT_COMPONENT', - gridPosition: { row: 0, ...COL_3, rowSpan: 9 }, - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - TOP_PR_AUTHORS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - { - universalIdentifier: '5e4a8c1d-7f93-4b2e-9d6c-3a8f1b5e7d4c', - title: 'Top Reviewers', - type: 'FRONT_COMPONENT', - gridPosition: { row: 9, ...COL_3, rowSpan: 9 }, - configuration: { - configurationType: 'FRONT_COMPONENT', - frontComponentUniversalIdentifier: - TOP_REVIEWERS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, - }, - }, - ], - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/command-menu-items/fetch-project-items.command-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/command-menu-items/fetch-project-items.command-menu-item.ts deleted file mode 100644 index 52fbaedbb6..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/command-menu-items/fetch-project-items.command-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineCommandMenuItem, objectMetadataItem } from 'twenty-sdk/define'; - -export default defineCommandMenuItem({ - universalIdentifier: '719cfe1c-d570-4c8c-89e6-88671c6ba1ea', - frontComponentUniversalIdentifier: '7c397b0c-8b19-4fac-924a-8f6aa1dece78', - label: 'Fetch Project Items', - icon: 'IconLayoutKanban', - isPinned: false, - conditionalAvailabilityExpression: - objectMetadataItem.nameSingular === 'projectItem', -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/linked-issue-on-project-item.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/linked-issue-on-project-item.field.ts deleted file mode 100644 index f03e3ec74b..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/linked-issue-on-project-item.field.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { ISSUE_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/objects/issue.object'; -import { PROJECT_ITEMS_ON_ISSUE_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/issue/fields/project-items-on-issue.field'; - -export const LINKED_ISSUE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER = - '7a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d'; - -export default defineField({ - universalIdentifier: LINKED_ISSUE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'linkedIssue', - label: 'Linked Issue', - icon: 'IconBug', - relationTargetObjectMetadataUniversalIdentifier: ISSUE_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - PROJECT_ITEMS_ON_ISSUE_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'linkedIssueId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/linked-pr-on-project-item.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/linked-pr-on-project-item.field.ts deleted file mode 100644 index 85fcea65c8..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/linked-pr-on-project-item.field.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { PROJECT_ITEMS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/project-items-on-pull-request.field'; - -export const LINKED_PR_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER = - '9c4d5e6f-7a8b-4c9d-ae0f-1a2b3c4d5e6f'; - -export default defineField({ - universalIdentifier: LINKED_PR_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'linkedPullRequest', - label: 'Linked PR', - icon: 'IconGitPullRequest', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - PROJECT_ITEMS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'linkedPullRequestId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/main-assignee-on-project-item.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/main-assignee-on-project-item.field.ts deleted file mode 100644 index 03ddeb0ef4..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/fields/main-assignee-on-project-item.field.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { ASSIGNED_PROJECT_ITEMS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/fields/assigned-project-items-on-contributor.field'; - -export const MAIN_ASSIGNEE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER = - '5bc9d7b3-ca5a-4006-bd74-0420d1f3df85'; - -export default defineField({ - universalIdentifier: MAIN_ASSIGNEE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'mainAssignee', - label: 'Main Assignee', - icon: 'IconUser', - relationTargetObjectMetadataUniversalIdentifier: - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - ASSIGNED_PROJECT_ITEMS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'mainAssigneeId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/front-components/fetch-project-items.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/front-components/fetch-project-items.front-component.tsx deleted file mode 100644 index bf5b7a64cc..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/front-components/fetch-project-items.front-component.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useEffect, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - enqueueSnackbar, - unmountFrontComponent, - updateProgress, -} from 'twenty-sdk/front-component'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; - -type CountResponse = { - totalPages: number; - projects: Array<{ - owner: string; - number: number; - totalCount: number; - pages: number; - }>; -}; - -type FetchPageResponse = { - itemCount: number; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type SyncStatus = 'syncing' | 'done' | 'error'; - -const FetchProjectItems = () => { - const [status, setStatus] = useState('syncing'); - - useEffect(() => { - const run = async () => { - try { - const counts = (await callAppRoute( - '/github/count-project-items', - {}, - )) as CountResponse; - - if (counts.projects.length === 0) { - throw new Error( - 'No projects resolved. Set GITHUB_PROJECTS in the application variables (e.g. `twentyhq/24`).', - ); - } - - const totalPages = Math.max(counts.totalPages, 1); - let pagesProcessed = 0; - let totalItems = 0; - - for (const { owner, number } of counts.projects) { - let cursor: string | null = null; - let hasMore = true; - - while (hasMore) { - const data = (await callAppRoute('/github/fetch-project-items', { - owner, - number, - cursor, - })) as FetchPageResponse; - - totalItems += data.itemCount; - hasMore = data.hasMore && data.itemCount > 0; - cursor = data.endCursor; - pagesProcessed++; - - updateProgress(Math.min(Math.round((pagesProcessed / totalPages) * 100), 99)); - } - } - - updateProgress(100); - enqueueSnackbar({ - message: `Fetched ${totalItems} project items`, - variant: 'success', - }); - setStatus('done'); - } catch (err) { - const message = - err instanceof Error ? err.message : 'Failed to fetch project items'; - enqueueSnackbar({ message, variant: 'error' }); - setStatus('error'); - } finally { - unmountFrontComponent(); - } - }; - run(); - }, []); - - if (status === 'syncing') return
Fetching project items...
; - if (status === 'error') return
Failed to fetch project items.
; - return
Done
; -}; - -export default defineFrontComponent({ - universalIdentifier: '7c397b0c-8b19-4fac-924a-8f6aa1dece78', - name: 'Fetch Project Items', - description: 'Fetches project items from GitHub Projects V2', - isHeadless: true, - component: FetchProjectItems, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/count-project-items.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/count-project-items.ts deleted file mode 100644 index ae8e24c29c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/count-project-items.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { githubGraphqlOptional } from 'src/modules/github/connector/github-client'; - -const ORG_QUERY = ` -query($owner: String!, $number: Int!) { - organization(login: $owner) { - projectV2(number: $number) { - items { totalCount } - } - } -}`; - -const USER_QUERY = ORG_QUERY.replace( - 'organization(login: $owner)', - 'user(login: $owner)', -); - -type OrgResponse = { - organization: { projectV2: { items: { totalCount: number } } | null } | null; -}; - -type UserResponse = { - user: { projectV2: { items: { totalCount: number } } | null } | null; -}; - -export async function countProjectItems( - owner: string, - projectNumber: number, -): Promise { - const orgData = await githubGraphqlOptional(ORG_QUERY, { - owner, - number: projectNumber, - }); - const orgCount = orgData?.organization?.projectV2?.items.totalCount; - if (typeof orgCount === 'number') return orgCount; - - const userData = await githubGraphqlOptional(USER_QUERY, { - owner, - number: projectNumber, - }); - return userData?.user?.projectV2?.items.totalCount ?? 0; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fetch-project-item-by-node-id.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fetch-project-item-by-node-id.ts deleted file mode 100644 index caccd64cb6..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fetch-project-item-by-node-id.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { githubGraphql } from 'src/modules/github/connector/github-client'; -import type { ProjectV2Item } from 'src/modules/github/project-item/types/project-v2-item'; -import { PROJECT_V2_ITEM_FRAGMENT } from 'src/modules/github/project-item/graphql/github/fragments'; - -const QUERY = ` -query($id: ID!) { - node(id: $id) { - ... on ProjectV2Item { - ${PROJECT_V2_ITEM_FRAGMENT} - } - } -}`; - -type Response = { - node: ProjectV2Item | null; -}; - -export async function fetchProjectItemByNodeId( - nodeId: string, -): Promise { - try { - const data = await githubGraphql(QUERY, { id: nodeId }); - return data.node; - } catch (err) { - const msg = err instanceof Error ? err.message : ''; - if (msg.includes('Could not resolve') || msg.includes('global id')) { - return null; - } - throw err; - } -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fetch-project-items.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fetch-project-items.ts deleted file mode 100644 index 068af361e8..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fetch-project-items.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - EMPTY_PAGE, - type GithubPage, - githubGraphqlOptional, -} from 'src/modules/github/connector/github-client'; -import type { ProjectV2Item } from 'src/modules/github/project-item/types/project-v2-item'; -import { PROJECT_V2_ITEM_FRAGMENT } from 'src/modules/github/project-item/graphql/github/fragments'; - -const ORG_QUERY = ` -query($owner: String!, $number: Int!, $cursor: String) { - organization(login: $owner) { - projectV2(number: $number) { - items(first: 100, after: $cursor) { - totalCount - pageInfo { hasNextPage endCursor } - nodes { ${PROJECT_V2_ITEM_FRAGMENT} } - } - } - } -}`; - -const USER_QUERY = ORG_QUERY.replace( - 'organization(login: $owner)', - 'user(login: $owner)', -); - -type ItemsConnection = GithubPage; - -type OrgResponse = { - organization: { projectV2: { items: ItemsConnection } | null } | null; -}; - -type UserResponse = { - user: { projectV2: { items: ItemsConnection } | null } | null; -}; - -export async function fetchProjectItems( - owner: string, - projectNumber: number, - cursor: string | null = null, -): Promise<{ - items: ProjectV2Item[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}> { - const orgData = await githubGraphqlOptional(ORG_QUERY, { - owner, - number: projectNumber, - cursor, - }); - - let conn: ItemsConnection | undefined = - orgData?.organization?.projectV2?.items; - - if (!conn) { - const userData = await githubGraphqlOptional(USER_QUERY, { - owner, - number: projectNumber, - cursor, - }); - conn = userData?.user?.projectV2?.items; - } - - if (!conn) { - console.warn( - `[github-gql] project ${owner}/${projectNumber} returned no items (project does not exist or the fine-grained PAT lacks Organization → Projects: Read for "${owner}", and may also need to be approved by an org admin).`, - ); - return { items: [], ...EMPTY_PAGE }; - } - - return { - items: conn.nodes, - totalCount: conn.totalCount, - hasMore: conn.pageInfo.hasNextPage, - endCursor: conn.pageInfo.endCursor, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fragments.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fragments.ts deleted file mode 100644 index 6d6c35832a..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/github/fragments.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const PROJECT_V2_ITEM_FRAGMENT = ` - id - content { - __typename - ... on Issue { - title - number - url - repository { nameWithOwner } - } - ... on PullRequest { - title - number - url - repository { nameWithOwner } - } - ... on DraftIssue { - title - } - } - fieldValues(first: 20) { - nodes { - __typename - ... on ProjectV2ItemFieldSingleSelectValue { - name - field { ... on ProjectV2SingleSelectField { name } } - } - ... on ProjectV2ItemFieldIterationValue { - title - field { ... on ProjectV2IterationField { name } } - } - ... on ProjectV2ItemFieldTextValue { - text - field { ... on ProjectV2Field { name } } - } - ... on ProjectV2ItemFieldNumberValue { - number - field { ... on ProjectV2Field { name } } - } - ... on ProjectV2ItemFieldUserValue { - users(first: 10) { nodes { login } } - field { ... on ProjectV2Field { name } } - } - } - } -`; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/mutations/batch-upsert.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/mutations/batch-upsert.ts deleted file mode 100644 index 0d2511effb..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/graphql/mutations/batch-upsert.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { chunkedBatchCreate } from 'src/modules/shared/twenty-client'; -import type { ProjectItemRow } from 'src/modules/github/project-item/types/project-item-row'; - -export async function batchUpsertProjectItems( - items: Array<{ - name: string; - githubProjectItemId: string; - status: string; - sprint: string; - assignees: string; - priority: string | null; - mainAssigneeId: string | null; - linkedIssueId: string | null; - linkedPullRequestId: string | null; - githubUrl: { primaryLinkLabel: string; primaryLinkUrl: string; secondaryLinks: null } | null; - repo: string; - }>, -): Promise { - return chunkedBatchCreate('createProjectItems', items, { - id: true, - githubProjectItemId: true, - name: true, - status: true, - mainAssigneeId: true, - linkedIssueId: true, - linkedPullRequestId: true, - githubUrl: { primaryLinkLabel: true, primaryLinkUrl: true }, - }) as Promise; -} 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 deleted file mode 100644 index 2b96b8ce5f..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/count-project-items.logic-function.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { defineLogicFunction } from 'twenty-sdk/define'; -import { type RoutePayload } from 'twenty-sdk/logic-function'; -import { - getGithubProjects, - type GithubProject, -} from 'src/modules/github/connector/config'; -import { countProjectItems } from 'src/modules/github/project-item/graphql/github/count-project-items'; - -const PAGE_SIZE = 100; - -type CountProjectItemsPayload = { - projects?: GithubProject[]; -}; - -const handler = async (event: RoutePayload) => { - const bodyProjects = event.body?.projects; - const projects = - bodyProjects && bodyProjects.length > 0 - ? bodyProjects - : getGithubProjects(); - - const results: Array = - []; - let totalPages = 0; - - for (const { owner, number } of projects) { - const totalCount = await countProjectItems(owner, number); - const pages = Math.max(Math.ceil(totalCount / PAGE_SIZE), 1); - results.push({ owner, number, totalCount, pages }); - totalPages += pages; - } - - return { totalPages, projects: results }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'f7a3e1b2-5c4d-4e6f-8a9b-0d1c2e3f4a5b', - name: 'count-project-items', - description: - 'Counts total project item pages across configured projects using GraphQL totalCount', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/github/count-project-items', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 6222569935..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/logic-functions/fetch-project-items.logic-function.ts +++ /dev/null @@ -1,58 +0,0 @@ -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'; -import { projectItemFromGraphql } from 'src/modules/github/project-item/normalizers'; -import { isFixtureAllowed } from 'src/modules/shared/fixtures'; - -export type FetchProjectItemsFixturePage = { - items: ProjectV2Item[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type FetchProjectItemsPayload = { - owner: string; - number: number; - cursor?: string | null; - fixturePage?: FetchProjectItemsFixturePage; -}; - -const handler = async (event: RoutePayload) => { - const { owner, number, cursor = null, fixturePage } = event.body ?? {}; - if (!owner || !number) { - return { error: 'owner and number are required' }; - } - - const result = - fixturePage && isFixtureAllowed() - ? fixturePage - : await fetchProjectItems(owner, number, cursor); - const { items, totalCount, hasMore, endCursor } = result; - - if (items.length === 0) { - return { itemCount: 0, totalCount, hasMore: false, endCursor: null }; - } - - const itemData = await Promise.all(items.map(projectItemFromGraphql)); - - await batchUpsertProjectItems(itemData); - - return { itemCount: items.length, totalCount, hasMore, endCursor }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'acb300d4-d4ec-491c-b314-3d4db90a49c5', - name: 'fetch-project-items', - description: - 'Fetches one page of project items from GitHub Projects V2 GraphQL API and batch upserts them', - timeoutSeconds: 300, - handler, - httpRouteTriggerSettings: { - path: '/github/fetch-project-items', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/navigation-menu-items/project-items.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/navigation-menu-items/project-items.navigation-menu-item.ts deleted file mode 100644 index 6b9f762b5e..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/navigation-menu-items/project-items.navigation-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; - -export default defineNavigationMenuItem({ - universalIdentifier: '25d3a916-9a70-478f-8d83-ef336e582fbc', - position: 2, - type: NavigationMenuItemType.OBJECT, - targetObjectUniversalIdentifier: PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/normalizers.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/normalizers.ts deleted file mode 100644 index e0bac19f10..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/normalizers.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; -import { toLinksField } from 'src/modules/shared/types'; -import type { ProjectV2Item } from 'src/modules/github/project-item/types/project-v2-item'; -import { - extractFieldValue, - extractAssigneeLogins, -} from 'src/modules/github/project-item/utils/extract-field-value'; -import { findIssueByNumberAndRepo } from 'src/modules/github/issue/graphql/queries/find-by-number-and-repo'; -import { - findPullRequestByGithubNumber, - findPullRequestByRepoAndNumber, -} from 'src/modules/github/pull-request/graphql/queries/find-by-github-number'; -import { findContributorByGhLogin } from 'src/modules/github/contributor/graphql/queries/find-by-gh-login'; - -const STATUS_MAP: Record = { - 'No Status': 'NO_STATUS', - Backlog: 'BACKLOG', - Todo: 'TODO', - 'In Progress': 'IN_PROGRESS', - 'In Review': 'IN_REVIEW', - Done: 'DONE', -}; - -const PRIORITY_MAP: Record = { - Low: 'LOW', - Medium: 'MEDIUM', - High: 'HIGH', - Critical: 'CRITICAL', -}; - -export type ProjectItemUpsertInput = { - name: string; - githubProjectItemId: string; - status: string; - sprint: string; - assignees: string; - priority: string | null; - mainAssigneeId: string | null; - linkedIssueId: string | null; - linkedPullRequestId: string | null; - githubUrl: LinksFieldValue | null; - repo: string; -}; - -export async function projectItemFromGraphql( - item: ProjectV2Item, -): Promise { - const title = - item.content?.title ?? - (extractFieldValue(item, 'Title') || 'Untitled'); - const rawStatus = extractFieldValue(item, 'Status'); - const status = STATUS_MAP[rawStatus] ?? 'NO_STATUS'; - const sprint = - extractFieldValue(item, 'Sprint') || - extractFieldValue(item, 'Iteration'); - const assigneeLogins = extractAssigneeLogins(item); - const assignees = assigneeLogins.join(', '); - const rawPriority = extractFieldValue(item, 'Priority'); - const priority = PRIORITY_MAP[rawPriority] ?? null; - - let mainAssigneeId: string | null = null; - if (assigneeLogins.length > 0) { - const contributor = await findContributorByGhLogin(assigneeLogins[0]); - mainAssigneeId = contributor?.id ?? null; - } - - let linkedIssueId: string | null = null; - let linkedPullRequestId: string | null = null; - let repo = ''; - let githubUrl: LinksFieldValue | null = null; - - if (item.content) { - const contentType = item.content.__typename; - repo = item.content.repository?.nameWithOwner ?? ''; - - if (contentType === 'Issue' && item.content.number) { - const issue = await findIssueByNumberAndRepo(item.content.number, repo); - linkedIssueId = issue?.id ?? null; - if (item.content.url) { - githubUrl = toLinksField(item.content.url, `#${item.content.number}`); - } - } else if (contentType === 'PullRequest' && item.content.number) { - const pr = repo - ? await findPullRequestByRepoAndNumber(repo, item.content.number) - : await findPullRequestByGithubNumber(item.content.number); - linkedPullRequestId = pr?.id ?? null; - if (item.content.url) { - githubUrl = toLinksField(item.content.url, `#${item.content.number}`); - } - } - } - - return { - name: title, - githubProjectItemId: item.id, - status, - sprint, - assignees, - priority, - mainAssigneeId, - linkedIssueId, - linkedPullRequestId, - githubUrl, - repo, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/objects/project-item.object.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/objects/project-item.object.ts deleted file mode 100644 index b339f48913..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/objects/project-item.object.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { defineObject, FieldType } from 'twenty-sdk/define'; - -export const PROJECT_ITEM_UNIVERSAL_IDENTIFIER = - '1c4c36a5-586c-4ead-9f3e-5e9718ba6231'; - -export const PROJECT_ITEM_NAME_FIELD_UNIVERSAL_IDENTIFIER = - '215cdbbd-8606-4c13-9025-3ebe912eaa32'; - -export const PROJECT_ITEM_GITHUB_ID_FIELD_UNIVERSAL_IDENTIFIER = - '2d3e4f5a-6b7c-4d8e-af9b-0c1d2e3f4a5b'; - -export const PROJECT_ITEM_SPRINT_FIELD_UNIVERSAL_IDENTIFIER = - 'e3708df4-6b6b-45f6-83fb-9d47c5bc5a25'; - -export const PROJECT_ITEM_ASSIGNEES_FIELD_UNIVERSAL_IDENTIFIER = - '3e4f5a6b-7c8d-4e9f-b0ac-1d2e3f4a5b6c'; - -export const PROJECT_ITEM_GITHUB_URL_FIELD_UNIVERSAL_IDENTIFIER = - '2db040a6-f2af-4e7d-9b70-d453f3cd99b7'; - -export const PROJECT_ITEM_REPO_FIELD_UNIVERSAL_IDENTIFIER = - '589d6aeb-8384-4d52-9d71-d63b7fd94ec7'; - -enum ProjectItemStatus { - NO_STATUS = 'NO_STATUS', - BACKLOG = 'BACKLOG', - TODO = 'TODO', - IN_PROGRESS = 'IN_PROGRESS', - IN_REVIEW = 'IN_REVIEW', - DONE = 'DONE', -} - -export const PROJECT_ITEM_STATUS_FIELD_UNIVERSAL_IDENTIFIER = - '54034cfc-83d9-478d-849c-98c91a819342'; - -enum ProjectItemPriority { - LOW = 'LOW', - MEDIUM = 'MEDIUM', - HIGH = 'HIGH', - CRITICAL = 'CRITICAL', -} - -export const PROJECT_ITEM_PRIORITY_FIELD_UNIVERSAL_IDENTIFIER = - '952a601c-b2da-4b84-9c7b-a9b42fcb7d95'; - -export const PROJECT_ITEM_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '597b4141-487f-5ad3-87c2-49f0a1679856'; - -export default defineObject({ - universalIdentifier: PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - nameSingular: 'projectItem', - namePlural: 'projectItems', - labelSingular: 'Project Item', - labelPlural: 'Project Items', - icon: 'IconLayoutKanban', - labelIdentifierFieldMetadataUniversalIdentifier: - PROJECT_ITEM_NAME_FIELD_UNIVERSAL_IDENTIFIER, - fields: [ - { - universalIdentifier: PROJECT_ITEM_NAME_FIELD_UNIVERSAL_IDENTIFIER, - name: 'name', - type: FieldType.TEXT, - label: 'Name', - icon: 'IconTextCaption', - }, - { - universalIdentifier: PROJECT_ITEM_GITHUB_ID_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubProjectItemId', - type: FieldType.TEXT, - label: 'GitHub Item ID', - icon: 'IconHash', - isUnique: true, - }, - { - universalIdentifier: PROJECT_ITEM_STATUS_FIELD_UNIVERSAL_IDENTIFIER, - name: 'status', - type: FieldType.SELECT, - label: 'Status', - icon: 'IconCircleDot', - options: [ - { - value: ProjectItemStatus.NO_STATUS, - label: 'No Status', - position: 0, - color: 'gray', - }, - { - value: ProjectItemStatus.BACKLOG, - label: 'Backlog', - position: 1, - color: 'gray', - }, - { - value: ProjectItemStatus.TODO, - label: 'Todo', - position: 2, - color: 'blue', - }, - { - value: ProjectItemStatus.IN_PROGRESS, - label: 'In Progress', - position: 3, - color: 'yellow', - }, - { - value: ProjectItemStatus.IN_REVIEW, - label: 'In Review', - position: 4, - color: 'turquoise', - }, - { - value: ProjectItemStatus.DONE, - label: 'Done', - position: 5, - color: 'green', - }, - ], - }, - { - universalIdentifier: PROJECT_ITEM_SPRINT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'sprint', - type: FieldType.TEXT, - label: 'Sprint', - icon: 'IconRun', - }, - { - universalIdentifier: PROJECT_ITEM_ASSIGNEES_FIELD_UNIVERSAL_IDENTIFIER, - name: 'assignees', - type: FieldType.TEXT, - label: 'Assignees', - icon: 'IconUsers', - }, - { - universalIdentifier: PROJECT_ITEM_PRIORITY_FIELD_UNIVERSAL_IDENTIFIER, - name: 'priority', - type: FieldType.SELECT, - label: 'Priority', - icon: 'IconFlag', - options: [ - { - value: ProjectItemPriority.LOW, - label: 'Low', - position: 0, - color: 'green', - }, - { - value: ProjectItemPriority.MEDIUM, - label: 'Medium', - position: 1, - color: 'turquoise', - }, - { - value: ProjectItemPriority.HIGH, - label: 'High', - position: 2, - color: 'red', - }, - { - value: ProjectItemPriority.CRITICAL, - label: 'Critical', - position: 3, - color: 'red', - }, - ], - }, - { - universalIdentifier: PROJECT_ITEM_GITHUB_URL_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubUrl', - type: FieldType.LINKS, - label: 'GitHub URL', - icon: 'IconLink', - }, - { - universalIdentifier: PROJECT_ITEM_REPO_FIELD_UNIVERSAL_IDENTIFIER, - name: 'repo', - type: FieldType.TEXT, - label: 'Repository', - icon: 'IconFolder', - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/github-project-v2-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/github-project-v2-item.ts deleted file mode 100644 index c91adc6216..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/github-project-v2-item.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type GitHubProjectV2Item = { - id: number; - node_id: string; - project_node_id?: string; - content_node_id?: string; - content_type?: 'Issue' | 'PullRequest' | 'DraftIssue'; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/project-item-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/project-item-row.ts deleted file mode 100644 index 8445526743..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/project-item-row.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; - -export type ProjectItemRow = { - id: string; - name?: string | null; - githubProjectItemId?: string | null; - status?: string | null; - sprint?: string | null; - assignees?: string | null; - priority?: string | null; - mainAssigneeId?: string | null; - linkedIssueId?: string | null; - linkedPullRequestId?: string | null; - githubUrl?: LinksFieldValue | null; - repo?: string | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/project-v2-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/project-v2-item.ts deleted file mode 100644 index 91fb168578..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/types/project-v2-item.ts +++ /dev/null @@ -1,42 +0,0 @@ -type FieldRef = { name: string }; - -export type ProjectV2FieldValue = - | { - __typename: 'ProjectV2ItemFieldSingleSelectValue'; - name: string; - field: FieldRef; - } - | { - __typename: 'ProjectV2ItemFieldIterationValue'; - title: string; - field: FieldRef; - } - | { - __typename: 'ProjectV2ItemFieldTextValue'; - text: string; - field: FieldRef; - } - | { - __typename: 'ProjectV2ItemFieldNumberValue'; - number: number; - field: FieldRef; - } - | { - __typename: 'ProjectV2ItemFieldUserValue'; - users: { nodes: Array<{ login: string }> }; - field: FieldRef; - }; - -export type ProjectV2Item = { - id: string; - content: { - __typename: string; - title?: string; - number?: number; - url?: string; - repository?: { nameWithOwner: string }; - } | null; - fieldValues: { - nodes: Array; - }; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/utils/extract-field-value.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/utils/extract-field-value.ts deleted file mode 100644 index c09a393472..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/utils/extract-field-value.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { ProjectV2Item } from 'src/modules/github/project-item/types/project-v2-item'; - -export function extractAssigneeLogins(item: ProjectV2Item): string[] { - for (const fv of item.fieldValues.nodes) { - if (fv.field?.name !== 'Assignees') continue; - if (fv.__typename === 'ProjectV2ItemFieldUserValue') { - return fv.users.nodes.map((u) => u.login); - } - } - return []; -} - -export function extractFieldValue( - item: ProjectV2Item, - fieldName: string, -): string { - for (const fv of item.fieldValues.nodes) { - if (fv.field?.name !== fieldName) continue; - - switch (fv.__typename) { - case 'ProjectV2ItemFieldSingleSelectValue': - return fv.name; - case 'ProjectV2ItemFieldIterationValue': - return fv.title; - case 'ProjectV2ItemFieldTextValue': - return fv.text; - case 'ProjectV2ItemFieldNumberValue': - return String(fv.number); - case 'ProjectV2ItemFieldUserValue': - return fv.users.nodes.map((u) => u.login).join(', '); - } - } - return ''; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/views/all-project-items.view.ts b/packages/twenty-apps/community/github-connector/src/modules/github/project-item/views/all-project-items.view.ts deleted file mode 100644 index 1b8a8971e2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/project-item/views/all-project-items.view.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { - defineView, - ViewKey, - ViewSortDirection, - ViewType, -} from 'twenty-sdk/define'; -import { - PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_NAME_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_STATUS_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_SPRINT_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_ASSIGNEES_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_PRIORITY_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_GITHUB_URL_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_REPO_FIELD_UNIVERSAL_IDENTIFIER, - PROJECT_ITEM_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/project-item/objects/project-item.object'; -import { MAIN_ASSIGNEE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/fields/main-assignee-on-project-item.field'; -import { LINKED_ISSUE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/fields/linked-issue-on-project-item.field'; -import { LINKED_PR_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/fields/linked-pr-on-project-item.field'; - -export const ALL_PROJECT_ITEMS_VIEW_UNIVERSAL_IDENTIFIER = - '9bb6d69e-9411-4195-9042-8df2e4b72a11'; - -export default defineView({ - universalIdentifier: ALL_PROJECT_ITEMS_VIEW_UNIVERSAL_IDENTIFIER, - name: 'All Project Items', - objectUniversalIdentifier: PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - type: ViewType.TABLE, - icon: 'IconLayoutKanban', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: '19064693-70e0-4653-b6f6-572c3adfcc78', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 300, - }, - { - universalIdentifier: 'f7a1de3d-3989-4cb9-8e8e-0edd1b4d15e7', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_STATUS_FIELD_UNIVERSAL_IDENTIFIER, - position: 1, - isVisible: true, - size: 130, - }, - { - universalIdentifier: 'f4fae1f9-8480-4cfd-b493-9dbac84c6643', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_PRIORITY_FIELD_UNIVERSAL_IDENTIFIER, - position: 2, - isVisible: true, - size: 130, - }, - { - universalIdentifier: '5969adcf-1e89-4561-a052-0296058623f3', - fieldMetadataUniversalIdentifier: - MAIN_ASSIGNEE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - position: 3, - isVisible: true, - size: 180, - }, - { - universalIdentifier: '21868cca-dae2-45e7-aa92-e667703e8cfe', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_SPRINT_FIELD_UNIVERSAL_IDENTIFIER, - position: 4, - isVisible: true, - size: 150, - }, - { - universalIdentifier: 'bf877bd3-a788-4db5-89cd-db6fb74fe49d', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_ASSIGNEES_FIELD_UNIVERSAL_IDENTIFIER, - position: 5, - isVisible: true, - size: 200, - }, - { - universalIdentifier: '4c063477-ffe8-4799-bc50-bb9b2483cbab', - fieldMetadataUniversalIdentifier: - LINKED_ISSUE_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - position: 6, - isVisible: true, - size: 180, - }, - { - universalIdentifier: 'acba5c9a-fd97-42b6-b0f5-e8e3fb7d49c5', - fieldMetadataUniversalIdentifier: - LINKED_PR_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - position: 7, - isVisible: true, - size: 180, - }, - { - universalIdentifier: '931fac89-e99d-46dd-b316-85ff04356811', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_GITHUB_URL_FIELD_UNIVERSAL_IDENTIFIER, - position: 8, - isVisible: true, - size: 200, - }, - { - universalIdentifier: '11c446af-470a-40d4-96e8-9e91879accf4', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_REPO_FIELD_UNIVERSAL_IDENTIFIER, - position: 9, - isVisible: true, - size: 180, - }, - ], - sorts: [ - { - universalIdentifier: 'a7c181f1-2961-45b0-a700-dd9489b3420c', - fieldMetadataUniversalIdentifier: - PROJECT_ITEM_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - direction: ViewSortDirection.DESC, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/pull-request-on-review-event.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/pull-request-on-review-event.field.ts deleted file mode 100644 index 60f0ba5915..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/pull-request-on-review-event.field.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, -} from 'twenty-sdk/define'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { REVIEW_EVENTS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/review-events-on-pull-request.field'; - -export const PULL_REQUEST_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER = - '8bd96f9d-fa6b-431a-9264-000097b580f8'; - -export default defineField({ - universalIdentifier: PULL_REQUEST_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'pullRequest', - label: 'Pull Request', - icon: 'IconGitPullRequest', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEW_EVENTS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'pullRequestId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/review-on-review-event.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/review-on-review-event.field.ts deleted file mode 100644 index 2c1916498c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/review-on-review-event.field.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, -} from 'twenty-sdk/define'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { EVENTS_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/fields/events-on-review.field'; - -export const REVIEW_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER = - '1bc4170d-e998-4aac-bfea-4a6145d7d707'; - -export default defineField({ - universalIdentifier: REVIEW_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'review', - label: 'Review', - icon: 'IconEye', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - EVENTS_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'reviewId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/reviewer-on-review-event.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/reviewer-on-review-event.field.ts deleted file mode 100644 index 1ee48f85b2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/fields/reviewer-on-review-event.field.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, -} from 'twenty-sdk/define'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { REVIEW_EVENTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/fields/review-events-on-contributor.field'; - -export const REVIEWER_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER = - 'f5a6b7c8-9d0e-4f1a-b2c3-d4e5f6a7b8c9'; - -export default defineField({ - universalIdentifier: REVIEWER_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviewer', - label: 'Reviewer', - icon: 'IconUser', - relationTargetObjectMetadataUniversalIdentifier: - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEW_EVENTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'reviewerId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/graphql/mutations/batch-upsert.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/graphql/mutations/batch-upsert.ts deleted file mode 100644 index 0276fb8f2f..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/graphql/mutations/batch-upsert.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { chunkedBatchCreate } from 'src/modules/shared/twenty-client'; -import type { PullRequestReviewEventRow } from 'src/modules/github/pull-request-review-event/types/pull-request-review-event-row'; - -export async function batchUpsertReviewEvents( - items: Array<{ - githubReviewId: number; - title: string; - state: string; - submittedAt: string | null; - reviewerId: string | null; - pullRequestId: string; - reviewId?: string | null; - }>, -): Promise { - return chunkedBatchCreate('createPullRequestReviewEvents', items, { - id: true, - githubReviewId: true, - title: true, - state: true, - submittedAt: true, - reviewerId: true, - pullRequestId: true, - reviewId: true, - }) as Promise; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/graphql/queries/find-events-for-pair.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/graphql/queries/find-events-for-pair.ts deleted file mode 100644 index 48074fc7cc..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/graphql/queries/find-events-for-pair.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { getClient } from 'src/modules/shared/twenty-client'; - -export type ReviewEventForPair = { - id: string; - state: string; - submittedAt: string | null; -}; - -type Edge = { node: T }; -type Connection = { - edges: Edge[]; - pageInfo: { hasNextPage: boolean; endCursor: string | null }; -}; - -const PAGE_SIZE = 100; - -export async function findReviewEventsForPair( - pullRequestId: string, - reviewerId: string | null, -): Promise { - const client = getClient(); - const out: ReviewEventForPair[] = []; - let cursor: string | null = null; - - const reviewerFilter = - reviewerId === null - ? { reviewerId: { is: 'NULL' } } - : { reviewerId: { eq: reviewerId } }; - - for (;;) { - const res = await client.query({ - pullRequestReviewEvents: { - __args: { - filter: { - and: [{ pullRequestId: { eq: pullRequestId } }, reviewerFilter], - }, - orderBy: [{ submittedAt: 'AscNullsLast' }], - first: PAGE_SIZE, - after: cursor, - }, - edges: { - node: { id: true, state: true, submittedAt: true }, - }, - pageInfo: { hasNextPage: true, endCursor: true }, - }, - }); - - const conn = - (res.pullRequestReviewEvents as Connection) ?? { - edges: [], - pageInfo: { hasNextPage: false, endCursor: null }, - }; - - for (const edge of conn.edges) out.push(edge.node); - - if (!conn.pageInfo.hasNextPage || !conn.pageInfo.endCursor) break; - cursor = conn.pageInfo.endCursor; - } - - return out; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/navigation-menu-items/pull-request-review-events.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/navigation-menu-items/pull-request-review-events.navigation-menu-item.ts deleted file mode 100644 index 72be66c037..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/navigation-menu-items/pull-request-review-events.navigation-menu-item.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - defineNavigationMenuItem, - NavigationMenuItemType, -} from 'twenty-sdk/define'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; - -export default defineNavigationMenuItem({ - universalIdentifier: '2a3b4c5d-6e7f-4890-9abc-def012345679', - position: 5, - type: NavigationMenuItemType.OBJECT, - targetObjectUniversalIdentifier: - PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/normalizers.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/normalizers.ts deleted file mode 100644 index 4e7c94e908..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/normalizers.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { GitHubReview } from 'src/modules/github/pull-request-review-event/types/github-review'; - -export type ReviewEventState = - | 'APPROVED' - | 'CHANGES_REQUESTED' - | 'COMMENTED' - | 'DISMISSED'; - -export type ReviewEventCanonical = { - githubReviewId: number; - title: string; - state: ReviewEventState; - submittedAt: string | null; -}; - -export type GqlReviewLike = { - databaseId: number; - state: ReviewEventState; - submittedAt: string | null; - author: { login: string } | null; -}; - -export function buildReviewEventTitle(login: string, state: string): string { - return `${login} \u2014 ${state.toLowerCase()}`; -} - -export function reviewEventFromWebhook( - review: GitHubReview, -): ReviewEventCanonical { - return { - githubReviewId: review.id, - title: buildReviewEventTitle(review.user.login, review.state), - state: review.state, - submittedAt: review.submitted_at, - }; -} - -export function reviewEventFromGraphql( - review: GqlReviewLike, -): ReviewEventCanonical { - const login = review.author?.login ?? 'unknown'; - return { - githubReviewId: review.databaseId, - title: buildReviewEventTitle(login, review.state), - state: review.state, - submittedAt: review.submittedAt, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/objects/pull-request-review-event.object.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/objects/pull-request-review-event.object.ts deleted file mode 100644 index a137486947..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/objects/pull-request-review-event.object.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { defineObject, FieldType } from 'twenty-sdk/define'; - -export const PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER = - '7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d'; - -export const REVIEW_EVENT_TITLE_FIELD_UNIVERSAL_IDENTIFIER = - '8b9c0d1e-2f3a-4b4c-9d5e-6f7a8b9c0d1e'; - -export const REVIEW_EVENT_GITHUB_REVIEW_ID_FIELD_UNIVERSAL_IDENTIFIER = - '9c0d1e2f-3a4b-4c5d-ae6f-7a8b9c0d1e2f'; - -export const REVIEW_EVENT_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '0d1e2f3a-4b5c-4d6e-bf7a-8b9c0d1e2f3a'; - -enum ReviewEventState { - APPROVED = 'APPROVED', - CHANGES_REQUESTED = 'CHANGES_REQUESTED', - COMMENTED = 'COMMENTED', - DISMISSED = 'DISMISSED', -} - -export const REVIEW_EVENT_STATE_FIELD_UNIVERSAL_IDENTIFIER = - 'e5eeeaab-4ea9-4f9f-873d-9c1c1473ddb3'; - -export const PULL_REQUEST_REVIEW_EVENT_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'fc577b22-c927-5b0e-9d6d-532dd41cf6d2'; - -export default defineObject({ - universalIdentifier: PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - nameSingular: 'pullRequestReviewEvent', - namePlural: 'pullRequestReviewEvents', - labelSingular: 'Pull Request Review Event', - labelPlural: 'Pull Request Review Events', - icon: 'IconEye', - labelIdentifierFieldMetadataUniversalIdentifier: - REVIEW_EVENT_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - fields: [ - { - universalIdentifier: REVIEW_EVENT_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'title', - type: FieldType.TEXT, - label: 'Title', - icon: 'IconTextCaption', - }, - { - universalIdentifier: - REVIEW_EVENT_GITHUB_REVIEW_ID_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubReviewId', - type: FieldType.NUMBER, - label: 'GitHub Review ID', - icon: 'IconHash', - isUnique: true, - }, - { - universalIdentifier: REVIEW_EVENT_STATE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'state', - type: FieldType.SELECT, - label: 'State', - icon: 'IconCircleDot', - options: [ - { - value: ReviewEventState.APPROVED, - label: 'Approved', - position: 0, - color: 'green', - }, - { - value: ReviewEventState.CHANGES_REQUESTED, - label: 'Changes Requested', - position: 1, - color: 'red', - }, - { - value: ReviewEventState.COMMENTED, - label: 'Commented', - position: 2, - color: 'blue', - }, - { - value: ReviewEventState.DISMISSED, - label: 'Dismissed', - position: 3, - color: 'gray', - }, - ], - }, - { - universalIdentifier: REVIEW_EVENT_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'submittedAt', - type: FieldType.DATE_TIME, - label: 'Submitted At', - icon: 'IconCalendar', - isNullable: true, - defaultValue: null, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/types/github-review.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/types/github-review.ts deleted file mode 100644 index ef65ca870c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/types/github-review.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { GitHubUser } from 'src/modules/github/connector/github-user'; - -export type GitHubReview = { - id: number; - user: GitHubUser; - state: 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'DISMISSED'; - submitted_at: string; - body: string | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/types/pull-request-review-event-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/types/pull-request-review-event-row.ts deleted file mode 100644 index bf1aabb656..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/types/pull-request-review-event-row.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type PullRequestReviewEventRow = { - id: string; - title?: string | null; - githubReviewId?: number | null; - state?: string | null; - submittedAt?: string | null; - reviewerId?: string | null; - pullRequestId?: string | null; - reviewId?: string | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/views/all-pull-request-review-events.view.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/views/all-pull-request-review-events.view.ts deleted file mode 100644 index e25d0900b1..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review-event/views/all-pull-request-review-events.view.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { - defineView, - ViewKey, - ViewSortDirection, - ViewType, -} from 'twenty-sdk/define'; -import { - PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - REVIEW_EVENT_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_EVENT_STATE_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_EVENT_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - PULL_REQUEST_REVIEW_EVENT_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { REVIEWER_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/fields/reviewer-on-review-event.field'; -import { PULL_REQUEST_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/fields/pull-request-on-review-event.field'; - -export const ALL_REVIEW_EVENTS_VIEW_UNIVERSAL_IDENTIFIER = - 'a1b2c3d4-0000-4e5f-a6b7-c8d9e0f1a2b3'; - -export default defineView({ - universalIdentifier: ALL_REVIEW_EVENTS_VIEW_UNIVERSAL_IDENTIFIER, - name: 'All Review Events', - objectUniversalIdentifier: PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - type: ViewType.TABLE, - icon: 'IconEye', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: '69e69714-05a5-48cd-98b9-13e79ba602c5', - fieldMetadataUniversalIdentifier: - REVIEW_EVENT_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 250, - }, - { - universalIdentifier: 'ca794df5-74b0-4fc7-a2a7-05a72380e080', - fieldMetadataUniversalIdentifier: - REVIEW_EVENT_STATE_FIELD_UNIVERSAL_IDENTIFIER, - position: 1, - isVisible: true, - size: 150, - }, - { - universalIdentifier: '624b139e-46de-416d-97b2-527c07b3a738', - fieldMetadataUniversalIdentifier: - REVIEWER_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - position: 2, - isVisible: true, - size: 160, - }, - { - universalIdentifier: 'bcf9d3ac-d68c-410b-99f4-7e36f992f767', - fieldMetadataUniversalIdentifier: - PULL_REQUEST_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - position: 3, - isVisible: true, - size: 200, - }, - { - universalIdentifier: '798ec4a2-9e72-4d48-8010-fe5dcaa7fbed', - fieldMetadataUniversalIdentifier: - REVIEW_EVENT_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 4, - isVisible: true, - size: 180, - }, - ], - sorts: [ - { - universalIdentifier: '0c8f9d6f-1a2d-4b14-a35a-8ec65a0683e3', - fieldMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_EVENT_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - direction: ViewSortDirection.DESC, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/events-on-review.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/events-on-review.field.ts deleted file mode 100644 index e09fadfa0a..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/events-on-review.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { REVIEW_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/fields/review-on-review-event.field'; - -export const EVENTS_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER = - '846f70d2-5a2b-41e1-b10c-628f874837f2'; - -export default defineField({ - universalIdentifier: EVENTS_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviewEvents', - label: 'Review Events', - icon: 'IconHistory', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEW_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/pull-request-on-review.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/pull-request-on-review.field.ts deleted file mode 100644 index 687b40ef2d..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/pull-request-on-review.field.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, -} from 'twenty-sdk/define'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { REVIEWS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/reviews-on-pull-request.field'; - -export const PULL_REQUEST_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER = - '5cc6fd4f-400f-421b-8c24-ffbf08077dd8'; - -export default defineField({ - universalIdentifier: PULL_REQUEST_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'pullRequest', - label: 'Pull Request', - icon: 'IconGitPullRequest', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEWS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.CASCADE, - joinColumnName: 'pullRequestId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/reviewer-on-review.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/reviewer-on-review.field.ts deleted file mode 100644 index e3225ccf1a..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/fields/reviewer-on-review.field.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - defineField, - FieldType, - RelationType, - OnDeleteAction, -} from 'twenty-sdk/define'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { REVIEWS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/fields/reviews-on-contributor.field'; - -export const REVIEWER_FIELD_UNIVERSAL_IDENTIFIER = - '2272ecc4-90e1-4c66-96bd-a7ac20280943'; - -export default defineField({ - universalIdentifier: REVIEWER_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviewer', - label: 'Reviewer', - icon: 'IconUser', - relationTargetObjectMetadataUniversalIdentifier: - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - REVIEWS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'reviewerId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/graphql/mutations/batch-upsert.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/graphql/mutations/batch-upsert.ts deleted file mode 100644 index f27fde9374..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/graphql/mutations/batch-upsert.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { chunkedBatchCreate } from 'src/modules/shared/twenty-client'; -import type { PullRequestReviewRow } from 'src/modules/github/pull-request-review/types/pull-request-review-row'; -import type { ConsolidatedReviewUpsertInput } from 'src/modules/github/pull-request-review/utils/build-consolidated-row'; - -export async function batchUpsertConsolidatedReviews( - items: ConsolidatedReviewUpsertInput[], -): Promise { - return chunkedBatchCreate('createPullRequestReviews', items, { - id: true, - reviewKey: true, - title: true, - state: true, - firstSubmittedAt: true, - lastSubmittedAt: true, - eventCount: true, - reviewerId: true, - pullRequestId: true, - isSelfReview: true, - }) as Promise; -} 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 deleted file mode 100644 index feb9e4d03f..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/logic-functions/recompute-pull-request-reviews.logic-function.ts +++ /dev/null @@ -1,173 +0,0 @@ -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'; -import { - buildConsolidatedRow, - type ConsolidatedReviewUpsertInput, -} from 'src/modules/github/pull-request-review/utils/build-consolidated-row'; -import type { ReviewEventState } from 'src/modules/github/pull-request-review/utils/consolidate-reviews'; - -const PAGE_SIZE = 100; - -type Edge = { node: T }; -type Connection = { - edges: Edge[]; - pageInfo: { hasNextPage: boolean; endCursor: string | null }; -}; - -type ReviewEventNode = { - state: ReviewEventState; - submittedAt: string | null; - reviewerId: string | null; - pullRequestId: string | null; - reviewer: { ghLogin: string | null } | null; - pullRequest: { - githubNumber: number | null; - authorId: string | null; - } | null; -}; - -type GroupContext = { - pullRequestId: string; - reviewerId: string | null; - prNumber: number | null; - reviewerLogin: string | null; - prAuthorId: string | null; - events: { state: ReviewEventState; submittedAt: string | null }[]; -}; - -async function* paginate( - fetchPage: (cursor: string | null) => Promise>, -): AsyncGenerator { - let cursor: string | null = null; - let hasMore = true; - while (hasMore) { - const conn = await fetchPage(cursor); - yield conn.edges.map((e) => e.node); - hasMore = conn.pageInfo.hasNextPage; - cursor = conn.pageInfo.endCursor; - if (!cursor) break; - } -} - -const handler = async (_event: RoutePayload) => { - const handlerStart = Date.now(); - const client = getClient(); - - const groups = new Map(); - let eventCount = 0; - let skippedNoPr = 0; - - await timed('recompute-reviews:scan-events', async () => { - for await (const batch of paginate(async (cursor) => { - const res = await client.query({ - pullRequestReviewEvents: { - __args: { first: PAGE_SIZE, after: cursor }, - edges: { - node: { - state: true, - submittedAt: true, - reviewerId: true, - pullRequestId: true, - reviewer: { ghLogin: true }, - pullRequest: { githubNumber: true, authorId: true }, - }, - }, - pageInfo: { hasNextPage: true, endCursor: true }, - }, - }); - return ( - (res.pullRequestReviewEvents as Connection) ?? { - edges: [], - pageInfo: { hasNextPage: false, endCursor: null }, - } - ); - })) { - for (const node of batch) { - eventCount++; - if (!node.pullRequestId) { - skippedNoPr++; - continue; - } - const key = `${node.pullRequestId}:${node.reviewerId ?? 'unknown'}`; - let group = groups.get(key); - if (!group) { - group = { - pullRequestId: node.pullRequestId, - reviewerId: node.reviewerId, - prNumber: node.pullRequest?.githubNumber ?? null, - reviewerLogin: node.reviewer?.ghLogin ?? null, - prAuthorId: node.pullRequest?.authorId ?? null, - events: [], - }; - groups.set(key, group); - } - if (!group.reviewerLogin && node.reviewer?.ghLogin) { - group.reviewerLogin = node.reviewer.ghLogin; - } - if (group.prNumber === null && node.pullRequest?.githubNumber != null) { - group.prNumber = node.pullRequest.githubNumber; - } - if (group.prAuthorId === null && node.pullRequest?.authorId) { - group.prAuthorId = node.pullRequest.authorId; - } - group.events.push({ - state: node.state, - submittedAt: node.submittedAt, - }); - } - } - }); - - const rows: ConsolidatedReviewUpsertInput[] = []; - for (const group of groups.values()) { - if (group.events.length === 0) continue; - rows.push( - buildConsolidatedRow({ - pullRequestId: group.pullRequestId, - reviewerId: group.reviewerId, - prNumber: group.prNumber, - reviewerLogin: group.reviewerLogin, - events: group.events, - prAuthorId: group.prAuthorId, - }), - ); - } - - let upsertedCount = 0; - if (rows.length > 0) { - const recs = await timed(`recompute-reviews:upsert (${rows.length})`, () => - batchUpsertConsolidatedReviews(rows), - ); - upsertedCount = recs.length; - } - - const totalMs = Date.now() - handlerStart; - console.log( - `[recompute-reviews] done in ${totalMs}ms (events=${eventCount}, groups=${groups.size}, upserted=${upsertedCount}, skippedNoPr=${skippedNoPr})`, - ); - - return { - eventCount, - groupCount: groups.size, - upsertedCount, - skippedNoPr, - durationMs: totalMs, - }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'fd2cbfa6-89a2-40f4-9164-0f658525cf07', - name: 'recompute-pull-request-reviews', - description: - 'Backfill/repeat-safe: scans every PullRequestReviewEvent, groups by (pullRequestId, reviewerId), and upserts the consolidated PullRequestReview row using the "first substantive wins" rule.', - timeoutSeconds: 300, - handler, - httpRouteTriggerSettings: { - path: '/pull-request-reviews/recompute', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/navigation-menu-items/pull-request-reviews.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/navigation-menu-items/pull-request-reviews.navigation-menu-item.ts deleted file mode 100644 index ef843310a2..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/navigation-menu-items/pull-request-reviews.navigation-menu-item.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - defineNavigationMenuItem, - NavigationMenuItemType, -} from 'twenty-sdk/define'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; - -export default defineNavigationMenuItem({ - universalIdentifier: '1f2e3d4c-5b6a-4798-8abc-def012345678', - position: 4, - type: NavigationMenuItemType.OBJECT, - targetObjectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/objects/pull-request-review.object.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/objects/pull-request-review.object.ts deleted file mode 100644 index f4f0242cfc..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/objects/pull-request-review.object.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { defineObject, FieldType } from 'twenty-sdk/define'; - -export const PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER = - '3c9b62b5-88a6-4a05-8e62-7637987cc8bb'; - -export const REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER = - '0d0f01f1-bdb2-4dc9-a915-a2eacecdedce'; - -export const REVIEW_KEY_FIELD_UNIVERSAL_IDENTIFIER = - 'f96c80a8-d42f-414e-8eab-5c5684c0b948'; - -export const REVIEW_STATE_FIELD_UNIVERSAL_IDENTIFIER = - 'b93fabcc-67ea-4532-847c-2b015fe90ee5'; - -export const REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'd7157b19-b173-41ca-96a5-98b7cc4f379e'; - -export const REVIEW_LAST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'ea73e23e-6010-4cb4-b9e0-9dfe572e4b69'; - -export const REVIEW_EVENT_COUNT_FIELD_UNIVERSAL_IDENTIFIER = - '5bca3165-8033-41da-96bb-bf381e6db45f'; - -export const PULL_REQUEST_REVIEW_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'b4d61187-1b78-5d6e-9752-79f994ff6d55'; - -export const REVIEW_IS_SELF_REVIEW_FIELD_UNIVERSAL_IDENTIFIER = - 'c1f3e8a2-9b5d-4e7f-8a6c-2d4b6f8e1a93'; - -enum ReviewState { - APPROVED = 'APPROVED', - CHANGES_REQUESTED = 'CHANGES_REQUESTED', - COMMENTED = 'COMMENTED', - DISMISSED = 'DISMISSED', -} - -export default defineObject({ - universalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - nameSingular: 'pullRequestReview', - namePlural: 'pullRequestReviews', - labelSingular: 'Pull Request Review', - labelPlural: 'Pull Request Reviews', - icon: 'IconEye', - labelIdentifierFieldMetadataUniversalIdentifier: - REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - fields: [ - { - universalIdentifier: REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'title', - type: FieldType.TEXT, - label: 'Title', - icon: 'IconTextCaption', - }, - { - universalIdentifier: REVIEW_KEY_FIELD_UNIVERSAL_IDENTIFIER, - name: 'reviewKey', - type: FieldType.TEXT, - label: 'Review Key', - icon: 'IconKey', - isUnique: true, - }, - { - universalIdentifier: REVIEW_STATE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'state', - type: FieldType.SELECT, - label: 'State', - icon: 'IconCircleDot', - options: [ - { - value: ReviewState.APPROVED, - label: 'Approved', - position: 0, - color: 'green', - }, - { - value: ReviewState.CHANGES_REQUESTED, - label: 'Changes Requested', - position: 1, - color: 'red', - }, - { - value: ReviewState.COMMENTED, - label: 'Commented', - position: 2, - color: 'blue', - }, - { - value: ReviewState.DISMISSED, - label: 'Dismissed', - position: 3, - color: 'gray', - }, - ], - }, - { - universalIdentifier: - REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'firstSubmittedAt', - type: FieldType.DATE_TIME, - label: 'First Submitted At', - icon: 'IconCalendar', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: REVIEW_LAST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'lastSubmittedAt', - type: FieldType.DATE_TIME, - label: 'Last Submitted At', - icon: 'IconCalendarTime', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: REVIEW_EVENT_COUNT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'eventCount', - type: FieldType.NUMBER, - label: 'Event Count', - icon: 'IconHash', - defaultValue: 0, - }, - { - universalIdentifier: REVIEW_IS_SELF_REVIEW_FIELD_UNIVERSAL_IDENTIFIER, - name: 'isSelfReview', - type: FieldType.BOOLEAN, - label: 'Self review', - description: - 'True when the reviewer is the same contributor as the PR author. Self reviews are excluded from review-count aggregations (top reviewers, contributor stats, etc.) so contributors are credited only for reviews on other people\u2019s PRs.', - icon: 'IconUserCheck', - defaultValue: false, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/types/pull-request-review-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/types/pull-request-review-row.ts deleted file mode 100644 index 7493fb8cb4..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/types/pull-request-review-row.ts +++ /dev/null @@ -1,12 +0,0 @@ -export type PullRequestReviewRow = { - id: string; - title?: string | null; - reviewKey?: string | null; - state?: string | null; - firstSubmittedAt?: string | null; - lastSubmittedAt?: string | null; - eventCount?: number | null; - reviewerId?: string | null; - pullRequestId?: string | null; - isSelfReview?: boolean | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/build-consolidated-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/build-consolidated-row.ts deleted file mode 100644 index 30331aad3c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/build-consolidated-row.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - consolidateReviews, - type ReviewEventForConsolidation, -} from 'src/modules/github/pull-request-review/utils/consolidate-reviews'; - -export type ConsolidatedReviewUpsertInput = { - reviewKey: string; - title: string; - state: string; - firstSubmittedAt: string | null; - lastSubmittedAt: string | null; - eventCount: number; - reviewerId: string | null; - pullRequestId: string; - isSelfReview: boolean; -}; - -export type BuildConsolidatedRowParams = { - pullRequestId: string; - reviewerId: string | null; - prNumber: number | null; - reviewerLogin: string | null; - events: ReviewEventForConsolidation[]; - /** - * Author of the PR being reviewed. When non-null and equal to `reviewerId` - * the consolidated row is flagged as a self-review so downstream - * aggregations (top reviewers, contributor stats, etc.) can exclude it. - */ - prAuthorId?: string | null; -}; - -export const isSelfReview = ( - reviewerId: string | null, - prAuthorId: string | null | undefined, -): boolean => - reviewerId !== null && - prAuthorId !== null && - prAuthorId !== undefined && - reviewerId === prAuthorId; - -export const buildReviewKey = ( - pullRequestId: string, - reviewerId: string | null, -): string => `${pullRequestId}:${reviewerId ?? 'unknown'}`; - -export const buildConsolidatedTitle = ( - reviewerLogin: string | null, - prNumber: number | null, -): string => { - const login = reviewerLogin ?? 'unknown'; - const number = typeof prNumber === 'number' ? prNumber : '?'; - return `${login} on PR #${number}`; -}; - -export const buildConsolidatedRow = ( - params: BuildConsolidatedRowParams, -): ConsolidatedReviewUpsertInput => { - const verdict = consolidateReviews(params.events); - return { - reviewKey: buildReviewKey(params.pullRequestId, params.reviewerId), - title: buildConsolidatedTitle(params.reviewerLogin, params.prNumber), - state: verdict.state, - firstSubmittedAt: verdict.firstSubmittedAt, - lastSubmittedAt: verdict.lastSubmittedAt, - eventCount: verdict.eventCount, - reviewerId: params.reviewerId, - pullRequestId: params.pullRequestId, - isSelfReview: isSelfReview(params.reviewerId, params.prAuthorId ?? null), - }; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/consolidate-reviews.integration-test.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/consolidate-reviews.integration-test.ts deleted file mode 100644 index e200d2039a..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/consolidate-reviews.integration-test.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { describe, expect, it } from 'vitest'; - -import { - consolidateReviews, - type ReviewEventForConsolidation, -} from 'src/modules/github/pull-request-review/utils/consolidate-reviews'; -import { - buildConsolidatedRow, - buildReviewKey, - buildConsolidatedTitle, - isSelfReview, -} from 'src/modules/github/pull-request-review/utils/build-consolidated-row'; - -const evt = ( - state: ReviewEventForConsolidation['state'], - submittedAt: string | null, -): ReviewEventForConsolidation => ({ state, submittedAt }); - -describe('consolidateReviews', () => { - it('throws on empty input', () => { - expect(() => consolidateReviews([])).toThrow(); - }); - - it('returns a verdict for a single COMMENTED event', () => { - const verdict = consolidateReviews([evt('COMMENTED', '2025-04-01T10:00:00Z')]); - expect(verdict).toEqual({ - state: 'COMMENTED', - firstSubmittedAt: '2025-04-01T10:00:00Z', - lastSubmittedAt: '2025-04-01T10:00:00Z', - eventCount: 1, - }); - }); - - it('picks the earliest substantive event when COMMENTED → APPROVED → COMMENTED', () => { - const verdict = consolidateReviews([ - evt('COMMENTED', '2025-04-01T10:00:00Z'), - evt('APPROVED', '2025-04-02T10:00:00Z'), - evt('COMMENTED', '2025-04-03T10:00:00Z'), - ]); - expect(verdict).toEqual({ - state: 'APPROVED', - firstSubmittedAt: '2025-04-02T10:00:00Z', - lastSubmittedAt: '2025-04-03T10:00:00Z', - eventCount: 3, - }); - }); - - it('keeps CHANGES_REQUESTED when it precedes APPROVED', () => { - const verdict = consolidateReviews([ - evt('CHANGES_REQUESTED', '2025-04-01T08:00:00Z'), - evt('APPROVED', '2025-04-05T08:00:00Z'), - ]); - expect(verdict.state).toBe('CHANGES_REQUESTED'); - expect(verdict.firstSubmittedAt).toBe('2025-04-01T08:00:00Z'); - expect(verdict.lastSubmittedAt).toBe('2025-04-05T08:00:00Z'); - expect(verdict.eventCount).toBe(2); - }); - - it('falls back to first event when only DISMISSED entries exist', () => { - const verdict = consolidateReviews([ - evt('DISMISSED', '2025-04-02T08:00:00Z'), - evt('DISMISSED', '2025-04-03T08:00:00Z'), - ]); - expect(verdict.state).toBe('DISMISSED'); - expect(verdict.firstSubmittedAt).toBe('2025-04-02T08:00:00Z'); - expect(verdict.lastSubmittedAt).toBe('2025-04-03T08:00:00Z'); - expect(verdict.eventCount).toBe(2); - }); - - it('handles only COMMENTED entries', () => { - const verdict = consolidateReviews([ - evt('COMMENTED', '2025-04-01T10:00:00Z'), - evt('COMMENTED', '2025-04-02T10:00:00Z'), - ]); - expect(verdict.state).toBe('COMMENTED'); - expect(verdict.firstSubmittedAt).toBe('2025-04-01T10:00:00Z'); - expect(verdict.lastSubmittedAt).toBe('2025-04-02T10:00:00Z'); - }); - - it('is order-independent', () => { - const a = consolidateReviews([ - evt('COMMENTED', '2025-04-01T10:00:00Z'), - evt('APPROVED', '2025-04-02T10:00:00Z'), - ]); - const b = consolidateReviews([ - evt('APPROVED', '2025-04-02T10:00:00Z'), - evt('COMMENTED', '2025-04-01T10:00:00Z'), - ]); - expect(a).toEqual(b); - }); - - it('handles null submittedAt by treating it as the earliest', () => { - const verdict = consolidateReviews([ - evt('APPROVED', '2025-04-01T10:00:00Z'), - evt('COMMENTED', null), - ]); - expect(verdict.eventCount).toBe(2); - expect(verdict.state).toBe('APPROVED'); - }); -}); - -describe('buildReviewKey', () => { - it('joins pullRequestId and reviewerId with a colon', () => { - expect(buildReviewKey('pr-1', 'rev-2')).toBe('pr-1:rev-2'); - }); - - it('uses "unknown" when reviewerId is null', () => { - expect(buildReviewKey('pr-1', null)).toBe('pr-1:unknown'); - }); -}); - -describe('buildConsolidatedTitle', () => { - it('formats reviewer login + PR number', () => { - expect(buildConsolidatedTitle('alice', 42)).toBe('alice on PR #42'); - }); - - it('falls back gracefully on missing data', () => { - expect(buildConsolidatedTitle(null, null)).toBe('unknown on PR #?'); - }); -}); - -describe('buildConsolidatedRow', () => { - it('produces the expected upsert payload', () => { - const row = buildConsolidatedRow({ - pullRequestId: 'pr-1', - reviewerId: 'rev-2', - prNumber: 7, - reviewerLogin: 'alice', - events: [ - evt('COMMENTED', '2025-04-01T10:00:00Z'), - evt('APPROVED', '2025-04-02T10:00:00Z'), - evt('COMMENTED', '2025-04-03T10:00:00Z'), - ], - }); - expect(row).toEqual({ - reviewKey: 'pr-1:rev-2', - title: 'alice on PR #7', - state: 'APPROVED', - firstSubmittedAt: '2025-04-02T10:00:00Z', - lastSubmittedAt: '2025-04-03T10:00:00Z', - eventCount: 3, - reviewerId: 'rev-2', - pullRequestId: 'pr-1', - isSelfReview: false, - }); - }); - - it('flags isSelfReview when prAuthorId equals reviewerId', () => { - const row = buildConsolidatedRow({ - pullRequestId: 'pr-1', - reviewerId: 'alice', - prNumber: 7, - reviewerLogin: 'alice', - prAuthorId: 'alice', - events: [evt('COMMENTED', '2025-04-01T10:00:00Z')], - }); - expect(row.isSelfReview).toBe(true); - }); - - it('does not flag isSelfReview when prAuthorId differs from reviewerId', () => { - const row = buildConsolidatedRow({ - pullRequestId: 'pr-1', - reviewerId: 'alice', - prNumber: 7, - reviewerLogin: 'alice', - prAuthorId: 'bob', - events: [evt('APPROVED', '2025-04-01T10:00:00Z')], - }); - expect(row.isSelfReview).toBe(false); - }); - - it('does not flag isSelfReview when prAuthorId is missing', () => { - const row = buildConsolidatedRow({ - pullRequestId: 'pr-1', - reviewerId: 'alice', - prNumber: 7, - reviewerLogin: 'alice', - events: [evt('APPROVED', '2025-04-01T10:00:00Z')], - }); - expect(row.isSelfReview).toBe(false); - }); - - it('does not flag isSelfReview when reviewerId is null (ghost reviewer)', () => { - const row = buildConsolidatedRow({ - pullRequestId: 'pr-1', - reviewerId: null, - prNumber: 7, - reviewerLogin: null, - prAuthorId: null, - events: [evt('COMMENTED', '2025-04-01T10:00:00Z')], - }); - expect(row.isSelfReview).toBe(false); - }); -}); - -describe('isSelfReview', () => { - it('returns true only when both ids are present and equal', () => { - expect(isSelfReview('a', 'a')).toBe(true); - }); - - it('returns false when ids differ', () => { - expect(isSelfReview('a', 'b')).toBe(false); - }); - - it('returns false when either side is null/undefined', () => { - expect(isSelfReview(null, 'a')).toBe(false); - expect(isSelfReview('a', null)).toBe(false); - expect(isSelfReview('a', undefined)).toBe(false); - expect(isSelfReview(null, null)).toBe(false); - }); -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/consolidate-reviews.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/consolidate-reviews.ts deleted file mode 100644 index c27f8ce421..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/utils/consolidate-reviews.ts +++ /dev/null @@ -1,54 +0,0 @@ -export type ReviewEventState = - | 'APPROVED' - | 'CHANGES_REQUESTED' - | 'COMMENTED' - | 'DISMISSED'; - -export type ReviewEventForConsolidation = { - state: ReviewEventState; - submittedAt: string | null; -}; - -export type ConsolidatedVerdict = { - state: ReviewEventState; - firstSubmittedAt: string | null; - lastSubmittedAt: string | null; - eventCount: number; -}; - -const SUBSTANTIVE_STATES: ReadonlyArray = [ - 'APPROVED', - 'CHANGES_REQUESTED', -]; - -const isSubstantive = (state: ReviewEventState): boolean => - SUBSTANTIVE_STATES.includes(state); - -const compareSubmittedAt = ( - a: ReviewEventForConsolidation, - b: ReviewEventForConsolidation, -): number => { - const av = a.submittedAt ?? ''; - const bv = b.submittedAt ?? ''; - if (av === bv) return 0; - return av < bv ? -1 : 1; -}; - -export const consolidateReviews = ( - events: ReviewEventForConsolidation[], -): ConsolidatedVerdict => { - if (events.length === 0) { - throw new Error('consolidateReviews called with empty events'); - } - - const sorted = [...events].sort(compareSubmittedAt); - const substantive = sorted.find((e) => isSubstantive(e.state)); - const chosen = substantive ?? sorted[0]; - - return { - state: chosen.state, - firstSubmittedAt: chosen.submittedAt, - lastSubmittedAt: sorted[sorted.length - 1].submittedAt, - eventCount: sorted.length, - }; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/views/all-pull-request-reviews.view.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/views/all-pull-request-reviews.view.ts deleted file mode 100644 index 71a05c4f0e..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request-review/views/all-pull-request-reviews.view.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { - defineView, - ViewKey, - ViewSortDirection, - ViewType, -} from 'twenty-sdk/define'; -import { - PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_STATE_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_LAST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - REVIEW_EVENT_COUNT_FIELD_UNIVERSAL_IDENTIFIER, - PULL_REQUEST_REVIEW_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { REVIEWER_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/fields/reviewer-on-review.field'; -import { PULL_REQUEST_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/fields/pull-request-on-review.field'; - -export const ALL_REVIEWS_VIEW_UNIVERSAL_IDENTIFIER = - '3559d020-bdf4-424b-9b6a-bc3cbb3d4502'; - -export default defineView({ - universalIdentifier: ALL_REVIEWS_VIEW_UNIVERSAL_IDENTIFIER, - name: 'All Reviews', - objectUniversalIdentifier: PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - type: ViewType.TABLE, - icon: 'IconEye', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: '63010877-3fd3-43f0-a105-66168a0c9782', - fieldMetadataUniversalIdentifier: - REVIEW_TITLE_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 240, - }, - { - universalIdentifier: 'c7f3ad93-e4bc-47d6-a3c3-664d9975bb60', - fieldMetadataUniversalIdentifier: - REVIEW_STATE_FIELD_UNIVERSAL_IDENTIFIER, - position: 1, - isVisible: true, - size: 150, - }, - { - universalIdentifier: 'ab0cd954-95e6-4e29-8a73-2ad614dc6b1f', - fieldMetadataUniversalIdentifier: REVIEWER_FIELD_UNIVERSAL_IDENTIFIER, - position: 2, - isVisible: true, - size: 160, - }, - { - universalIdentifier: '43440edf-ef04-413a-a39c-3ae70b839166', - fieldMetadataUniversalIdentifier: - PULL_REQUEST_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER, - position: 3, - isVisible: true, - size: 200, - }, - { - universalIdentifier: '20ddfe2f-0310-4c01-af85-b7c75d801e26', - fieldMetadataUniversalIdentifier: - REVIEW_FIRST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 4, - isVisible: true, - size: 170, - }, - { - universalIdentifier: '4e274558-49e9-465a-98c3-44177c6138c3', - fieldMetadataUniversalIdentifier: - REVIEW_LAST_SUBMITTED_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 5, - isVisible: true, - size: 170, - }, - { - universalIdentifier: 'd2964ab2-7e05-4ee0-9219-b88cfef78230', - fieldMetadataUniversalIdentifier: - REVIEW_EVENT_COUNT_FIELD_UNIVERSAL_IDENTIFIER, - position: 6, - isVisible: true, - size: 110, - }, - ], - sorts: [ - { - universalIdentifier: 'add26365-5060-411e-b0a7-1ce32155d4f1', - fieldMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - direction: ViewSortDirection.DESC, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/command-menu-items/fetch-prs.command-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/command-menu-items/fetch-prs.command-menu-item.ts deleted file mode 100644 index 06d7110b72..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/command-menu-items/fetch-prs.command-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineCommandMenuItem, objectMetadataItem } from 'twenty-sdk/define'; - -export default defineCommandMenuItem({ - universalIdentifier: '0b24e6d6-da0c-4c0e-8d88-5bfd7f3cd75a', - frontComponentUniversalIdentifier: '5e2ba8df-1db5-4964-94d3-44cccfd791a0', - label: 'Fetch Pull Requests', - icon: 'IconGitPullRequest', - isPinned: false, - conditionalAvailabilityExpression: - objectMetadataItem.nameSingular === 'pullRequest', -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/author-on-pull-request.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/author-on-pull-request.field.ts deleted file mode 100644 index 7b9c97c9e3..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/author-on-pull-request.field.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { AUTHORED_PRS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/fields/authored-prs-on-contributor.field'; - -export const AUTHOR_FIELD_UNIVERSAL_IDENTIFIER = - 'b2c3d4e5-1a2b-4c3d-8e5f-6a7b8c9d0e1f'; - -export default defineField({ - universalIdentifier: AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'author', - label: 'Author', - icon: 'IconUser', - relationTargetObjectMetadataUniversalIdentifier: - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - AUTHORED_PRS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'authorId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/merger-on-pull-request.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/merger-on-pull-request.field.ts deleted file mode 100644 index 0bc08d9232..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/merger-on-pull-request.field.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { defineField, FieldType, RelationType, OnDeleteAction } from 'twenty-sdk/define'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { CONTRIBUTOR_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/objects/contributor.object'; -import { MERGED_PULL_REQUESTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/contributor/fields/merged-pull-requests-on-contributor.field'; - -export const MERGER_FIELD_UNIVERSAL_IDENTIFIER = - '3a4a6af0-a6a7-4cb6-a91d-273a2842449e'; - -export default defineField({ - universalIdentifier: MERGER_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'merger', - label: 'Merger', - icon: 'IconUser', - relationTargetObjectMetadataUniversalIdentifier: - CONTRIBUTOR_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - MERGED_PULL_REQUESTS_ON_CONTRIBUTOR_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: OnDeleteAction.SET_NULL, - joinColumnName: 'mergerId', - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/project-items-on-pull-request.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/project-items-on-pull-request.field.ts deleted file mode 100644 index 01af097177..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/project-items-on-pull-request.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { PROJECT_ITEM_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/objects/project-item.object'; -import { LINKED_PR_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/project-item/fields/linked-pr-on-project-item.field'; - -export const PROJECT_ITEMS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER = - 'ad5e6f7a-8b9c-4d0e-bf1a-2b3c4d5e6f7a'; - -export default defineField({ - universalIdentifier: PROJECT_ITEMS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'projectItems', - label: 'Project Items', - icon: 'IconLayoutKanban', - relationTargetObjectMetadataUniversalIdentifier: - PROJECT_ITEM_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - LINKED_PR_ON_PROJECT_ITEM_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/review-events-on-pull-request.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/review-events-on-pull-request.field.ts deleted file mode 100644 index 2517afd737..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/review-events-on-pull-request.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/objects/pull-request-review-event.object'; -import { PULL_REQUEST_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review-event/fields/pull-request-on-review-event.field'; - -export const REVIEW_EVENTS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER = - '299b3ef8-cba0-4f16-a785-81728df09c46'; - -export default defineField({ - universalIdentifier: REVIEW_EVENTS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviewEvents', - label: 'Review Events', - icon: 'IconEye', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_EVENT_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - PULL_REQUEST_ON_REVIEW_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/reviews-on-pull-request.field.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/reviews-on-pull-request.field.ts deleted file mode 100644 index c38cee3654..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/fields/reviews-on-pull-request.field.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/objects/pull-request-review.object'; -import { PULL_REQUEST_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request-review/fields/pull-request-on-review.field'; - -export const REVIEWS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER = - '9a089f03-76fe-40ba-9a4d-39b84ba11d03'; - -export default defineField({ - universalIdentifier: REVIEWS_ON_PR_FIELD_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - type: FieldType.RELATION, - name: 'reviews', - label: 'Reviews', - icon: 'IconEye', - relationTargetObjectMetadataUniversalIdentifier: - PULL_REQUEST_REVIEW_UNIVERSAL_IDENTIFIER, - relationTargetFieldMetadataUniversalIdentifier: - PULL_REQUEST_ON_REVIEW_FIELD_UNIVERSAL_IDENTIFIER, - universalSettings: { - relationType: RelationType.ONE_TO_MANY, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/front-components/fetch-prs.front-component.tsx b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/front-components/fetch-prs.front-component.tsx deleted file mode 100644 index d19ec28d72..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/front-components/fetch-prs.front-component.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { useEffect, useState } from 'react'; -import { defineFrontComponent } from 'twenty-sdk/define'; -import { - enqueueSnackbar, - unmountFrontComponent, - updateProgress, -} from 'twenty-sdk/front-component'; -import { callAppRoute } from 'src/modules/shared/call-app-route'; -import { retry } from 'src/modules/shared/retry'; - -type CountResponse = { - totalPages: number; - repos: Array<{ owner: string; repo: string; totalCount: number; pages: number }>; -}; - -type FetchPageResponse = { - prCount: number; - reviewCount: number; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type SyncStatus = 'syncing' | 'done' | 'error'; - -const FetchPrs = () => { - const [status, setStatus] = useState('syncing'); - - useEffect(() => { - const run = async () => { - try { - const counts = (await callAppRoute( - '/github/count-prs', - {}, - )) as CountResponse; - - if (counts.repos.length === 0) { - throw new Error( - 'No repos resolved. Set GITHUB_REPOS in the application variables.', - ); - } - - const totalPages = Math.max(counts.totalPages, 1); - let pagesProcessed = 0; - let totalPrs = 0; - let totalReviews = 0; - - for (const { owner, repo } of counts.repos) { - let cursor: string | null = null; - let hasMore = true; - - while (hasMore) { - const cursorTag = cursor ? `@${cursor.slice(0, 8)}` : ''; - const data = (await retry( - `fetch-prs ${owner}/${repo}${cursorTag}`, - () => - callAppRoute('/github/fetch-prs', { - owner, - repo, - cursor, - }), - )) as FetchPageResponse; - - totalPrs += data.prCount; - totalReviews += data.reviewCount; - hasMore = data.hasMore && data.prCount > 0; - cursor = data.endCursor; - pagesProcessed++; - - updateProgress(Math.min(Math.round((pagesProcessed / totalPages) * 100), 99)); - } - } - - updateProgress(100); - enqueueSnackbar({ - message: `Fetched ${totalPrs} PRs and ${totalReviews} reviews`, - variant: 'success', - }); - setStatus('done'); - } catch (err) { - const message = - err instanceof Error ? err.message : 'Failed to fetch pull requests'; - enqueueSnackbar({ message, variant: 'error' }); - setStatus('error'); - } finally { - unmountFrontComponent(); - } - }; - run(); - }, []); - - if (status === 'syncing') return
Fetching pull requests...
; - if (status === 'error') return
Failed to fetch pull requests.
; - return
Done
; -}; - -export default defineFrontComponent({ - universalIdentifier: '5e2ba8df-1db5-4964-94d3-44cccfd791a0', - name: 'Fetch Pull Requests', - description: 'Fetches pull requests and reviews from GitHub repos', - isHeadless: true, - component: FetchPrs, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/github/count-pull-requests.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/github/count-pull-requests.ts deleted file mode 100644 index a167242ef3..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/github/count-pull-requests.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { githubGraphql } from 'src/modules/github/connector/github-client'; - -const QUERY = ` -query($owner: String!, $name: String!) { - repository(owner: $owner, name: $name) { - pullRequests(states: [OPEN, CLOSED, MERGED]) { - totalCount - } - } -}`; - -type Response = { - repository: { pullRequests: { totalCount: number } } | null; -}; - -export async function countPullRequests( - owner: string, - name: string, -): Promise { - try { - const data = await githubGraphql(QUERY, { owner, name }); - return data.repository?.pullRequests.totalCount ?? 0; - } catch { - return 0; - } -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/github/fetch-pull-requests.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/github/fetch-pull-requests.ts deleted file mode 100644 index 622fd74f4f..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/github/fetch-pull-requests.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - EMPTY_PAGE, - type GithubPage, - githubGraphql, -} from 'src/modules/github/connector/github-client'; - -const QUERY = ` -query($owner: String!, $name: String!, $cursor: String) { - repository(owner: $owner, name: $name) { - pullRequests(first: 100, states: [OPEN, CLOSED, MERGED], orderBy: {field: CREATED_AT, direction: DESC}, after: $cursor) { - totalCount - pageInfo { hasNextPage endCursor } - nodes { - number - title - url - state - merged - mergedAt - closedAt - createdAt - author { login avatarUrl ... on User { databaseId } } - mergedBy { login avatarUrl ... on User { databaseId } } - reviews(first: 100) { - nodes { - databaseId - state - submittedAt - author { login avatarUrl ... on User { databaseId } } - } - } - } - } - } -}`; - -export type GqlPullRequest = { - number: number; - title: string; - url: string; - state: 'OPEN' | 'CLOSED' | 'MERGED'; - merged: boolean; - mergedAt: string | null; - closedAt: string | null; - createdAt: string; - author: { login: string; avatarUrl?: string | null; databaseId?: number } | null; - mergedBy: { login: string; avatarUrl?: string | null; databaseId?: number } | null; - reviews: { - nodes: Array<{ - databaseId: number; - state: 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'DISMISSED'; - submittedAt: string | null; - author: { login: string; avatarUrl?: string | null; databaseId?: number } | null; - }>; - }; -}; - -type Response = { - repository: { pullRequests: GithubPage } | null; -}; - -export async function fetchPullRequests( - owner: string, - name: string, - cursor: string | null = null, -): Promise<{ - prs: GqlPullRequest[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}> { - const data = await githubGraphql(QUERY, { owner, name, cursor }); - const conn = data.repository?.pullRequests; - if (!conn) return { prs: [], ...EMPTY_PAGE }; - - return { - prs: conn.nodes, - totalCount: conn.totalCount, - hasMore: conn.pageInfo.hasNextPage, - endCursor: conn.pageInfo.endCursor, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/mutations/batch-upsert.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/mutations/batch-upsert.ts deleted file mode 100644 index 92d4241c4f..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/mutations/batch-upsert.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { chunkedBatchCreate } from 'src/modules/shared/twenty-client'; -import type { LinksFieldValue } from 'src/modules/shared/types'; -import type { PullRequestRow } from 'src/modules/github/pull-request/types/pull-request-row'; - -export async function batchUpsertPullRequests( - items: Array<{ - githubNumber: number; - uniqueIdentifier: string; - name: string; - url: LinksFieldValue; - state: string; - mergedAt: string | null; - closedAt: string | null; - githubCreatedAt: string | null; - authorId: string | null; - mergerId: string | null; - }>, -): Promise { - return chunkedBatchCreate('createPullRequests', items, { - id: true, - githubNumber: true, - uniqueIdentifier: true, - name: true, - state: true, - }) as Promise; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/mutations/update-pull-request.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/mutations/update-pull-request.ts deleted file mode 100644 index d9db8d7304..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/mutations/update-pull-request.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getClient } from 'src/modules/shared/twenty-client'; - -export async function updatePullRequest( - id: string, - data: Record, -): Promise { - const client = getClient(); - await client.mutation({ - updatePullRequest: { - __args: { id, data }, - id: true, - }, - }); -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/queries/find-by-github-number.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/queries/find-by-github-number.ts deleted file mode 100644 index f58fedf1b5..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/graphql/queries/find-by-github-number.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { getClient } from 'src/modules/shared/twenty-client'; -import type { PullRequestRow } from 'src/modules/github/pull-request/types/pull-request-row'; -import { buildPrUniqueIdentifier } from 'src/modules/github/pull-request/normalizers'; - -const PR_NODE_SELECTION = { - id: true, - name: true, - githubNumber: true, - uniqueIdentifier: true, - url: { primaryLinkLabel: true, primaryLinkUrl: true }, - state: true, - mergedAt: true, - closedAt: true, - githubCreatedAt: true, - authorId: true, - mergerId: true, -} as const; - -export async function findPullRequestByRepoAndNumber( - repo: string, - githubNumber: number, -): Promise { - const client = getClient(); - const uniqueIdentifier = buildPrUniqueIdentifier(repo, githubNumber); - const res = await client.query({ - pullRequests: { - __args: { - filter: { uniqueIdentifier: { eq: uniqueIdentifier } }, - first: 1, - }, - edges: { node: PR_NODE_SELECTION }, - }, - }); - - const node = res.pullRequests?.edges?.[0]?.node; - return (node as PullRequestRow | undefined) ?? null; -} - -export async function findPullRequestByGithubNumber( - githubNumber: number, -): Promise { - const client = getClient(); - const res = await client.query({ - pullRequests: { - __args: { - filter: { githubNumber: { eq: githubNumber } }, - first: 1, - }, - edges: { node: PR_NODE_SELECTION }, - }, - }); - - const node = res.pullRequests?.edges?.[0]?.node; - return (node as PullRequestRow | undefined) ?? null; -} 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 deleted file mode 100644 index 86b0080855..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/count-prs.logic-function.ts +++ /dev/null @@ -1,25 +0,0 @@ -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'; - -type CountPrsPayload = { - repos?: string[]; -}; - -const handler = async (event: RoutePayload) => - countAcrossRepos(event.body?.repos, countPullRequests, 'count-prs'); - -export default defineLogicFunction({ - universalIdentifier: '082227ae-2acc-4320-8d31-62ad6c443da6', - name: 'count-prs', - description: - 'Counts total PR pages across configured repos using GraphQL totalCount', - timeoutSeconds: 30, - handler, - httpRouteTriggerSettings: { - path: '/github/count-prs', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); 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 deleted file mode 100644 index 9a9af41978..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/logic-functions/fetch-prs.logic-function.ts +++ /dev/null @@ -1,246 +0,0 @@ -import { defineLogicFunction } from 'twenty-sdk/define'; -import { type RoutePayload } from 'twenty-sdk/logic-function'; -import { - fetchPullRequests, - type GqlPullRequest, -} from 'src/modules/github/pull-request/graphql/github/fetch-pull-requests'; -import { batchUpsertContributors } from 'src/modules/github/contributor/graphql/mutations/batch-upsert'; -import { batchUpsertPullRequests } from 'src/modules/github/pull-request/graphql/mutations/batch-upsert'; -import { batchUpsertReviewEvents } from 'src/modules/github/pull-request-review-event/graphql/mutations/batch-upsert'; -import { batchUpsertConsolidatedReviews } from 'src/modules/github/pull-request-review/graphql/mutations/batch-upsert'; -import { buildConsolidatedRow } from 'src/modules/github/pull-request-review/utils/build-consolidated-row'; -import { dedupeContributors } from 'src/modules/github/contributor/normalizers'; -import { pullRequestFromGraphql } from 'src/modules/github/pull-request/normalizers'; -import { - reviewEventFromGraphql, - type ReviewEventState, -} from 'src/modules/github/pull-request-review-event/normalizers'; -import { timed } from 'src/modules/shared/timing'; -import { isFixtureAllowed } from 'src/modules/shared/fixtures'; - -export type FetchPrsFixturePage = { - prs: GqlPullRequest[]; - totalCount: number; - hasMore: boolean; - endCursor: string | null; -}; - -type FetchPrsPayload = { - owner: string; - repo: string; - cursor?: string | null; - fixturePage?: FetchPrsFixturePage; -}; - -const handler = async (event: RoutePayload) => { - const handlerStart = Date.now(); - const { owner, repo, cursor = null, fixturePage } = event.body ?? {}; - if (!owner || !repo) { - return { error: 'owner and repo are required' }; - } - - const tag = `${owner}/${repo}${cursor ? `@${cursor.slice(0, 8)}` : ''}`; - console.log(`[fetch-prs] start ${tag}${fixturePage ? ' (fixture)' : ''}`); - - const result = - fixturePage && isFixtureAllowed() - ? fixturePage - : await timed(`fetch-prs:github ${tag}`, () => - fetchPullRequests(owner, repo, cursor), - ); - const { prs, totalCount, hasMore, endCursor } = result; - - if (prs.length === 0) { - console.log(`[fetch-prs] empty page for ${tag}`); - return { - prCount: 0, - reviewCount: 0, - totalCount, - hasMore: false, - endCursor: null, - }; - } - - const allUsers = prs.flatMap((pr) => [ - pr.author, - pr.mergedBy, - ...pr.reviews.nodes.map((r) => r.author), - ]); - const contributorInputs = dedupeContributors(allUsers); - const contributors = await timed( - `fetch-prs:upsertContributors ${tag} (${contributorInputs.length})`, - () => batchUpsertContributors(contributorInputs), - ); - - const idByLogin = new Map(); - for (const c of contributors) { - if (c.ghLogin) idByLogin.set(c.ghLogin, c.id); - } - - const fullRepo = `${owner}/${repo}`; - - const prData = prs.map((pr) => ({ - ...pullRequestFromGraphql(pr, fullRepo), - authorId: pr.author ? (idByLogin.get(pr.author.login) ?? null) : null, - mergerId: pr.mergedBy ? (idByLogin.get(pr.mergedBy.login) ?? null) : null, - })); - - const prRecords = await timed( - `fetch-prs:upsertPullRequests ${tag} (${prData.length})`, - () => batchUpsertPullRequests(prData), - ); - - const prIdByNumber = new Map(); - for (const rec of prRecords) { - if (rec.githubNumber) prIdByNumber.set(rec.githubNumber, rec.id); - } - - type ReviewEventInput = { - githubReviewId: number; - title: string; - state: string; - submittedAt: string | null; - reviewerId: string | null; - pullRequestId: string; - }; - - type GroupKey = string; - type GroupContext = { - pullRequestId: string; - reviewerId: string | null; - prNumber: number; - reviewerLogin: string | null; - prAuthorId: string | null; - events: { state: ReviewEventState; submittedAt: string | null }[]; - }; - - const authorIdByPullRequestId = new Map(); - for (const pr of prData) { - const id = prIdByNumber.get(pr.githubNumber); - if (id) authorIdByPullRequestId.set(id, pr.authorId); - } - - let skippedReviews = 0; - const reviewEventData: ReviewEventInput[] = []; - const groups = new Map(); - - for (const pr of prs) { - const pullRequestId = prIdByNumber.get(pr.number); - if (!pullRequestId) { - skippedReviews += pr.reviews.nodes.length; - continue; - } - for (const review of pr.reviews.nodes) { - const reviewerId = review.author - ? (idByLogin.get(review.author.login) ?? null) - : null; - const canonical = reviewEventFromGraphql(review); - reviewEventData.push({ - ...canonical, - reviewerId, - pullRequestId, - }); - const key = `${pullRequestId}:${reviewerId ?? 'unknown'}`; - let group = groups.get(key); - if (!group) { - group = { - pullRequestId, - reviewerId, - prNumber: pr.number, - reviewerLogin: review.author?.login ?? null, - prAuthorId: authorIdByPullRequestId.get(pullRequestId) ?? null, - events: [], - }; - groups.set(key, group); - } - if (!group.reviewerLogin && review.author?.login) { - group.reviewerLogin = review.author.login; - } - group.events.push({ - state: canonical.state, - submittedAt: canonical.submittedAt, - }); - } - } - - if (skippedReviews > 0) { - console.log( - `[fetch-prs] skipped ${skippedReviews} reviews with no matching PR for ${tag}`, - ); - } - - if (reviewEventData.length > 0) { - await timed( - `fetch-prs:upsertReviewEvents ${tag} (${reviewEventData.length})`, - () => batchUpsertReviewEvents(reviewEventData), - ); - } - - let consolidatedCount = 0; - if (groups.size > 0) { - const consolidatedRows = Array.from(groups.values()).map((group) => - buildConsolidatedRow({ - pullRequestId: group.pullRequestId, - reviewerId: group.reviewerId, - prNumber: group.prNumber, - reviewerLogin: group.reviewerLogin, - events: group.events, - prAuthorId: group.prAuthorId, - }), - ); - const consolidatedRecords = await timed( - `fetch-prs:upsertConsolidatedReviews ${tag} (${consolidatedRows.length})`, - () => batchUpsertConsolidatedReviews(consolidatedRows), - ); - consolidatedCount = consolidatedRecords.length; - - const reviewIdByKey = new Map(); - for (const rec of consolidatedRecords) { - if (rec.reviewKey && rec.id) reviewIdByKey.set(rec.reviewKey, rec.id); - } - - const eventBackfill: ReviewEventInput[] = reviewEventData - .map((evt) => { - const key = `${evt.pullRequestId}:${evt.reviewerId ?? 'unknown'}`; - const reviewId = reviewIdByKey.get(key); - if (!reviewId) return null; - return { ...evt, reviewId }; - }) - .filter((x): x is ReviewEventInput & { reviewId: string } => x !== null); - - if (eventBackfill.length > 0) { - await timed( - `fetch-prs:backfillReviewIds ${tag} (${eventBackfill.length})`, - () => batchUpsertReviewEvents(eventBackfill), - ); - } - } - - const totalMs = Date.now() - handlerStart; - console.log( - `[fetch-prs] done ${tag} in ${totalMs}ms (prs=${prs.length}, reviewEvents=${reviewEventData.length}, consolidatedReviews=${consolidatedCount}, hasMore=${hasMore})`, - ); - - return { - prCount: prs.length, - reviewCount: reviewEventData.length, - consolidatedReviewCount: consolidatedCount, - totalCount, - hasMore, - endCursor, - }; -}; - -export default defineLogicFunction({ - universalIdentifier: 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', - name: 'fetch-prs', - description: - 'Fetches one page of pull requests with reviews via GitHub GraphQL API and batch upserts them', - timeoutSeconds: 300, - handler, - httpRouteTriggerSettings: { - path: '/github/fetch-prs', - httpMethod: 'POST', - isAuthRequired: true, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/navigation-menu-items/pull-requests.navigation-menu-item.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/navigation-menu-items/pull-requests.navigation-menu-item.ts deleted file mode 100644 index 3c83dbf892..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/navigation-menu-items/pull-requests.navigation-menu-item.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; -import { PULL_REQUEST_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/objects/pull-request.object'; -import { GITHUB_FOLDER_UNIVERSAL_IDENTIFIER } from 'src/modules/github/navigation-menu-items/github-folder.navigation-menu-item'; - -export default defineNavigationMenuItem({ - universalIdentifier: 'bafa56a5-fd74-4c6f-ab44-d933877edb93', - position: 0, - type: NavigationMenuItemType.OBJECT, - targetObjectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - folderUniversalIdentifier: GITHUB_FOLDER_UNIVERSAL_IDENTIFIER, -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/normalizers.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/normalizers.ts deleted file mode 100644 index 9c7a20c50d..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/normalizers.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; -import { toLinksField } from 'src/modules/shared/types'; -import type { GitHubPullRequest } from 'src/modules/github/pull-request/types/github-pull-request'; -import type { GqlPullRequest } from 'src/modules/github/pull-request/graphql/github/fetch-pull-requests'; - -export type PullRequestState = 'OPEN' | 'CLOSED' | 'MERGED'; - -export type PullRequestCanonical = { - githubNumber: number; - uniqueIdentifier: string; - name: string; - url: LinksFieldValue; - state: PullRequestState; - mergedAt: string | null; - closedAt: string | null; - githubCreatedAt: string | null; -}; - -export function derivePrState( - merged: boolean | null | undefined, - state: string, -): PullRequestState { - if (merged) return 'MERGED'; - return state.toUpperCase() === 'CLOSED' ? 'CLOSED' : 'OPEN'; -} - -export function buildPrUniqueIdentifier( - repoFullName: string, - number: number, -): string { - return `${repoFullName}#${number}`; -} - -export function pullRequestFromWebhook( - pr: GitHubPullRequest, - repoFullName: string, -): PullRequestCanonical { - return { - githubNumber: pr.number, - uniqueIdentifier: buildPrUniqueIdentifier(repoFullName, pr.number), - name: pr.title, - url: toLinksField(pr.html_url, pr.title), - state: derivePrState(pr.merged, pr.state), - mergedAt: pr.merged_at, - closedAt: pr.closed_at, - githubCreatedAt: pr.created_at, - }; -} - -export function pullRequestFromGraphql( - pr: GqlPullRequest, - repoFullName: string, -): PullRequestCanonical { - return { - githubNumber: pr.number, - uniqueIdentifier: buildPrUniqueIdentifier(repoFullName, pr.number), - name: pr.title, - url: toLinksField(pr.url, pr.title), - state: derivePrState(pr.merged, pr.state), - mergedAt: pr.mergedAt, - closedAt: pr.closedAt, - githubCreatedAt: pr.createdAt, - }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/objects/pull-request.object.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/objects/pull-request.object.ts deleted file mode 100644 index 452b36d2f1..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/objects/pull-request.object.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { defineObject, FieldType } from 'twenty-sdk/define'; - -export const PULL_REQUEST_UNIVERSAL_IDENTIFIER = - 'bf539871-f118-4f35-b953-fa2ba528aea3'; - -export const PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER = - '5b394c0a-14db-4582-9513-6671661d64a5'; - -export const MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '064b38ec-ae2e-4d1b-bae9-d8d18329c8cd'; - -export const GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER = - 'a4b5c6d7-8e9f-4a0b-b1c2-d3e4f5a6b7c8'; - -export const URL_FIELD_UNIVERSAL_IDENTIFIER = - 'd4c3b2a1-6f5e-4d3c-9b8a-7f6e5d4c3b2a'; - -export const PR_UNIQUE_IDENTIFIER_FIELD_UNIVERSAL_IDENTIFIER = - '23b446f8-199a-4be4-881a-a475570b9590'; - -enum PullRequestState { - OPEN = 'OPEN', - CLOSED = 'CLOSED', - MERGED = 'MERGED', -} - -export const PR_STATE_FIELD_UNIVERSAL_IDENTIFIER = - 'e4f5a6b7-0c1d-4e2f-a3b4-c5d6e7f8a9b0'; - -export const PR_CLOSED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'f5a6b7c8-1d2e-4f3a-b4c5-d6e7f8a9b0c1'; - -export const PR_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - 'e38deee8-c57f-4b40-8c35-2b5d1fee759e'; - -export const PULL_REQUEST_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = - '78d142cf-7033-565d-b802-860bde5b0078'; - -export default defineObject({ - universalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - nameSingular: 'pullRequest', - namePlural: 'pullRequests', - labelSingular: 'Pull Request', - labelPlural: 'Pull Requests', - icon: 'IconTank', - labelIdentifierFieldMetadataUniversalIdentifier: - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - fields: [ - { - universalIdentifier: PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - name: 'name', - type: FieldType.TEXT, - label: 'Name', - icon: 'IconGitPullRequest', - }, - { - universalIdentifier: GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubNumber', - type: FieldType.NUMBER, - label: 'PR Number', - icon: 'IconHash', - }, - { - universalIdentifier: URL_FIELD_UNIVERSAL_IDENTIFIER, - name: 'url', - type: FieldType.LINKS, - label: 'URL', - icon: 'IconLink', - }, - { - universalIdentifier: PR_STATE_FIELD_UNIVERSAL_IDENTIFIER, - name: 'state', - type: FieldType.SELECT, - label: 'State', - icon: 'IconCircleDot', - options: [ - { - value: PullRequestState.OPEN, - label: 'Open', - position: 0, - color: 'green', - }, - { - value: PullRequestState.CLOSED, - label: 'Closed', - position: 1, - color: 'red', - }, - { - value: PullRequestState.MERGED, - label: 'Merged', - position: 2, - color: 'purple', - }, - ], - }, - { - universalIdentifier: MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'mergedAt', - type: FieldType.DATE_TIME, - label: 'Merged At', - icon: 'IconCalendarCheck', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: PR_CLOSED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'closedAt', - type: FieldType.DATE_TIME, - label: 'Closed At', - icon: 'IconCalendarOff', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: PR_GITHUB_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - name: 'githubCreatedAt', - type: FieldType.DATE_TIME, - label: 'GitHub Created At', - icon: 'IconCalendarPlus', - isNullable: true, - defaultValue: null, - }, - { - universalIdentifier: PR_UNIQUE_IDENTIFIER_FIELD_UNIVERSAL_IDENTIFIER, - name: 'uniqueIdentifier', - type: FieldType.TEXT, - label: 'Unique Identifier', - icon: 'IconFingerprint', - isUnique: true, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/types/github-pull-request.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/types/github-pull-request.ts deleted file mode 100644 index 2a8e84dcd7..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/types/github-pull-request.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { GitHubUser } from 'src/modules/github/connector/github-user'; - -export type GitHubPullRequest = { - number: number; - title: string; - html_url: string; - state: 'open' | 'closed'; - merged: boolean | null; - merged_at: string | null; - closed_at: string | null; - created_at: string; - user: GitHubUser; - merged_by: GitHubUser | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/types/pull-request-row.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/types/pull-request-row.ts deleted file mode 100644 index 446481e0f7..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/types/pull-request-row.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { LinksFieldValue } from 'src/modules/shared/types'; - -export type PullRequestRow = { - id: string; - name?: string | null; - githubNumber?: number | null; - uniqueIdentifier?: string | null; - url?: LinksFieldValue | null; - state?: string | null; - mergedAt?: string | null; - closedAt?: string | null; - githubCreatedAt?: string | null; - authorId?: string | null; - mergerId?: string | null; -}; diff --git a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/views/all-pull-requests.view.ts b/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/views/all-pull-requests.view.ts deleted file mode 100644 index a37ae5dfee..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/github/pull-request/views/all-pull-requests.view.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - defineView, - ViewKey, - ViewSortDirection, - ViewType, -} from 'twenty-sdk/define'; -import { - PULL_REQUEST_UNIVERSAL_IDENTIFIER, - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER, - PR_STATE_FIELD_UNIVERSAL_IDENTIFIER, - MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER, - PULL_REQUEST_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/modules/github/pull-request/objects/pull-request.object'; -import { AUTHOR_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/author-on-pull-request.field'; -import { MERGER_FIELD_UNIVERSAL_IDENTIFIER } from 'src/modules/github/pull-request/fields/merger-on-pull-request.field'; - -export const ALL_PULL_REQUESTS_VIEW_UNIVERSAL_IDENTIFIER = - 'e6a0bd60-08be-4973-81de-0e15ce8f9bf8'; - -export default defineView({ - universalIdentifier: ALL_PULL_REQUESTS_VIEW_UNIVERSAL_IDENTIFIER, - name: 'All Pull Requests', - objectUniversalIdentifier: PULL_REQUEST_UNIVERSAL_IDENTIFIER, - type: ViewType.TABLE, - icon: 'IconTank', - key: ViewKey.INDEX, - position: 0, - fields: [ - { - universalIdentifier: 'fd4fc905-f099-4553-8043-f677d9fbe0e0', - fieldMetadataUniversalIdentifier: - PULL_REQUEST_NAME_FIELD_UNIVERSAL_IDENTIFIER, - position: 0, - isVisible: true, - size: 250, - }, - { - universalIdentifier: '8848460a-5cb3-455d-91a3-d1916293d5f0', - fieldMetadataUniversalIdentifier: - GITHUB_NUMBER_FIELD_UNIVERSAL_IDENTIFIER, - position: 1, - isVisible: true, - size: 100, - }, - { - universalIdentifier: 'c05e00b7-a18a-44fb-970e-27b0b6ecdc7f', - fieldMetadataUniversalIdentifier: - PR_STATE_FIELD_UNIVERSAL_IDENTIFIER, - position: 2, - isVisible: true, - size: 120, - }, - { - universalIdentifier: 'f2834200-06c5-4702-b44d-ac04a292444c', - fieldMetadataUniversalIdentifier: AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - position: 3, - isVisible: true, - size: 160, - }, - { - universalIdentifier: '93d10703-194f-4e38-bdeb-813097f19d50', - fieldMetadataUniversalIdentifier: MERGER_FIELD_UNIVERSAL_IDENTIFIER, - position: 4, - isVisible: true, - size: 160, - }, - { - universalIdentifier: 'f90a284f-034a-42e8-9a08-38d22f5da983', - fieldMetadataUniversalIdentifier: MERGED_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 5, - isVisible: true, - size: 180, - }, - ], - sorts: [ - { - universalIdentifier: '7e7497fd-5bcd-43f8-bece-36e82f9f8f3f', - fieldMetadataUniversalIdentifier: - PULL_REQUEST_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER, - direction: ViewSortDirection.DESC, - }, - ], -}); diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/call-app-route.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/call-app-route.ts deleted file mode 100644 index 814b05074e..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/call-app-route.ts +++ /dev/null @@ -1,50 +0,0 @@ -const getEnv = (key: string): string | undefined => { - if (typeof process === 'undefined') return undefined; - return process.env?.[key]; -}; - -type ErrorResponse = { - messages?: string[]; - message?: string; - error?: string; - statusCode?: number; -}; - -function extractErrorMessage(text: string, status: number): string { - try { - const json = JSON.parse(text) as ErrorResponse; - if (json.messages?.length) return json.messages[0]; - return json.message ?? json.error ?? `Server error (${status})`; - } catch { - return text || `Server error (${status})`; - } -} - -export async function callAppRoute( - path: string, - body: Record, -): Promise { - const apiUrl = getEnv('TWENTY_API_URL') ?? ''; - const token = getEnv('TWENTY_APP_ACCESS_TOKEN'); - - const headers: Record = { - 'Content-Type': 'application/json', - }; - - if (token) { - headers['Authorization'] = `Bearer ${token}`; - } - - const res = await fetch(`${apiUrl}/s${path}`, { - method: 'POST', - headers, - body: JSON.stringify(body), - }); - - if (!res.ok) { - const text = await res.text(); - throw new Error(extractErrorMessage(text, res.status)); - } - - return res.json(); -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/fixtures.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/fixtures.ts deleted file mode 100644 index a848b3f491..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/fixtures.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isFixtureAllowed(): boolean { - return process.env.NODE_ENV !== 'production'; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/retry.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/retry.ts deleted file mode 100644 index c3fa2e58d8..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/retry.ts +++ /dev/null @@ -1,49 +0,0 @@ -export type RetryOptions = { - retries?: number; - baseDelayMs?: number; - maxDelayMs?: number; - shouldRetry?: (err: unknown) => boolean; -}; - -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - -export async function retry( - label: string, - fn: () => Promise, - options: RetryOptions = {}, -): Promise { - const requestedRetries = options.retries ?? 3; - const retries = Math.max( - Math.floor(Number.isFinite(requestedRetries) ? requestedRetries : 1), - 1, - ); - const baseDelayMs = options.baseDelayMs ?? 500; - const maxDelayMs = options.maxDelayMs ?? 5_000; - const shouldRetry = options.shouldRetry ?? (() => true); - - let lastErr: unknown; - for (let attempt = 1; attempt <= retries; attempt++) { - try { - return await fn(); - } catch (err) { - lastErr = err; - const msg = err instanceof Error ? err.message : String(err); - - if (attempt === retries || !shouldRetry(err)) { - console.log( - `[retry] ${label} attempt ${attempt}/${retries} failed (giving up): ${msg}`, - ); - throw err; - } - - const delay = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)); - const jittered = delay + Math.floor(Math.random() * baseDelayMs); - console.log( - `[retry] ${label} attempt ${attempt}/${retries} failed, retrying in ${jittered}ms: ${msg}`, - ); - await sleep(jittered); - } - } - - throw lastErr; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/timing.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/timing.ts deleted file mode 100644 index f24b3c9d5c..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/timing.ts +++ /dev/null @@ -1,17 +0,0 @@ -export async function timed( - label: string, - fn: () => Promise, -): Promise { - const start = Date.now(); - try { - const result = await fn(); - const ms = Date.now() - start; - console.log(`[timing] ${label} ok in ${ms}ms`); - return result; - } catch (err) { - const ms = Date.now() - start; - const msg = err instanceof Error ? err.message : String(err); - console.log(`[timing] ${label} FAILED in ${ms}ms: ${msg}`); - throw err; - } -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/twenty-client.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/twenty-client.ts deleted file mode 100644 index 20c4309a81..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/twenty-client.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; - -export const getClient = () => new CoreApiClient(); - -const BATCH_CHUNK_SIZE = 50; - -export async function chunkedBatchCreate>( - mutationName: string, - items: T[], - returnFields: Record>, -): Promise[]> { - if (items.length === 0) return []; - - const client = getClient(); - const results: Record[] = []; - const totalChunks = Math.ceil(items.length / BATCH_CHUNK_SIZE); - const overallStart = Date.now(); - - for (let i = 0; i < items.length; i += BATCH_CHUNK_SIZE) { - const chunk = items.slice(i, i + BATCH_CHUNK_SIZE); - const chunkIndex = Math.floor(i / BATCH_CHUNK_SIZE) + 1; - const chunkStart = Date.now(); - - const res = await client.mutation({ - [mutationName]: { - __args: { data: chunk, upsert: true }, - ...returnFields, - }, - }); - - const chunkMs = Date.now() - chunkStart; - console.log( - `[timing] ${mutationName} chunk ${chunkIndex}/${totalChunks} (${chunk.length} rows) in ${chunkMs}ms`, - ); - - const rows = res[mutationName]; - if (Array.isArray(rows)) { - results.push(...(rows as Record[])); - } - } - - const totalMs = Date.now() - overallStart; - console.log( - `[timing] ${mutationName} total ${items.length} rows in ${totalMs}ms (${totalChunks} chunk(s))`, - ); - - return results; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/types.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/types.ts deleted file mode 100644 index 46ed72233a..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type LinksFieldValue = { - primaryLinkLabel: string; - primaryLinkUrl: string; - secondaryLinks: null; -}; - -export function toLinksField(url: string, label = ''): LinksFieldValue { - return { primaryLinkLabel: label, primaryLinkUrl: url, secondaryLinks: null }; -} diff --git a/packages/twenty-apps/community/github-connector/src/modules/shared/universal-identifiers.ts b/packages/twenty-apps/community/github-connector/src/modules/shared/universal-identifiers.ts deleted file mode 100644 index cf4da82f3f..0000000000 --- a/packages/twenty-apps/community/github-connector/src/modules/shared/universal-identifiers.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const APP_DISPLAY_NAME = 'GitHub Connector'; -export const APP_DESCRIPTION = - 'Sync pull requests, reviews, issues, contributors and Projects (v2) items from GitHub into Twenty.'; -export const APP_ABOUT_DESCRIPTION = [ - 'Bring your GitHub activity into Twenty as native records you can view, filter, group and report on alongside the rest of your CRM data.', - '', - '**What gets synced**', - '- Pull requests, pull-request reviews and review events', - '- Issues', - '- Contributors (with avatars, profile links and bot detection)', - '- Projects (v2) items, including custom field values', - '', - '**How it stays in sync**', - '- Real-time updates via GitHub webhooks', - '- On-demand backfills via one-click commands on every record and view', - '- Works with any number of repositories and organizations', - '', - '**Authentication**', - '- GitHub App with private-key JWT (recommended for orgs)', - '- Fine-grained Personal Access Token (great for quick setups)', - '', - '**Out of the box**', - '- A GitHub Dashboard with PR / review counters, weekly histograms and top-contributor leaderboards', - '- A Contributor Stats panel on every contributor record (PRs authored, merged and reviewed over time)', - '- Pre-built views for pull requests, reviews, issues and project items', -].join('\n'); -export const APPLICATION_UNIVERSAL_IDENTIFIER = - '0c5b6bf2-2b41-4d5e-9f8a-3c4e6d7a8b9c'; -export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = - '4e7d8a5f-1b32-4f6a-bc91-2d9c8b7a6e54'; diff --git a/packages/twenty-apps/community/github-connector/tsconfig.json b/packages/twenty-apps/community/github-connector/tsconfig.json deleted file mode 100644 index d574c8c810..0000000000 --- a/packages/twenty-apps/community/github-connector/tsconfig.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "sourceMap": true, - "declaration": true, - "outDir": "./dist", - "rootDir": ".", - "jsx": "react-jsx", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "importHelpers": true, - "allowUnreachableCode": false, - "strict": true, - "alwaysStrict": true, - "noImplicitAny": true, - "strictBindCallApply": false, - "target": "es2018", - "module": "esnext", - "lib": ["es2020", "dom"], - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "resolveJsonModule": true, - "paths": { - "src/*": ["./src/*"], - "~/*": ["./*"] - } - }, - "exclude": [ - "node_modules", - "dist", - "**/*.test.ts", - "**/*.spec.ts", - "**/*.integration-test.ts" - ], - "references": [ - { - "path": "./tsconfig.spec.json" - } - ] -} diff --git a/packages/twenty-apps/community/github-connector/tsconfig.spec.json b/packages/twenty-apps/community/github-connector/tsconfig.spec.json deleted file mode 100644 index ea69a6c010..0000000000 --- a/packages/twenty-apps/community/github-connector/tsconfig.spec.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "composite": true, - "types": ["vitest/globals", "node"] - }, - "include": ["src/**/*.ts", "src/**/*.tsx"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/twenty-apps/community/github-connector/vitest.config.ts b/packages/twenty-apps/community/github-connector/vitest.config.ts deleted file mode 100644 index ae8883e69f..0000000000 --- a/packages/twenty-apps/community/github-connector/vitest.config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import tsconfigPaths from 'vite-tsconfig-paths'; -import { defineConfig } from 'vitest/config'; - -const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2021'; -const TWENTY_API_KEY = - process.env.TWENTY_API_KEY ?? - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjQ4OTE0NDk2MDAsImp0aSI6IjIwMjAyMDIwLWY0MDEtNGQ4YS1hNzMxLTY0ZDAwN2MyN2JhZCJ9.bfQjfyN0NEtTCLE_xPyNcwonDzlSXFoP8kdCQTdnuDc'; - -process.env.TWENTY_API_URL = TWENTY_API_URL; -process.env.TWENTY_API_KEY = TWENTY_API_KEY; - -export default defineConfig({ - plugins: [ - tsconfigPaths({ - projects: ['tsconfig.spec.json'], - ignoreConfigErrors: true, - }), - ], - test: { - testTimeout: 120_000, - hookTimeout: 120_000, - fileParallelism: false, - include: ['src/**/*.integration-test.ts'], - globalSetup: ['src/__tests__/global-setup.ts'], - env: { - TWENTY_API_URL, - TWENTY_API_KEY, - }, - }, -}); diff --git a/packages/twenty-apps/community/github-connector/yarn.lock b/packages/twenty-apps/community/github-connector/yarn.lock deleted file mode 100644 index fd892ceba2..0000000000 --- a/packages/twenty-apps/community/github-connector/yarn.lock +++ /dev/null @@ -1,2816 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@alcalzone/ansi-tokenize@npm:^0.2.4": - version: 0.2.5 - resolution: "@alcalzone/ansi-tokenize@npm:0.2.5" - dependencies: - ansi-styles: "npm:^6.2.1" - is-fullwidth-code-point: "npm:^5.0.0" - checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4 - languageName: node - linkType: hard - -"@emnapi/core@npm:1.10.0": - version: 1.10.0 - resolution: "@emnapi/core@npm:1.10.0" - dependencies: - "@emnapi/wasi-threads": "npm:1.2.1" - tslib: "npm:^2.4.0" - checksum: 10c0/f51d08227857b60632de7714d708124f0e100a1462dde6df8221760939aa3204a73193830371830fac0716f3ccd2129f2cac1b17cd7d7958bc4da9018a296edb - languageName: node - linkType: hard - -"@emnapi/runtime@npm:1.10.0": - version: 1.10.0 - resolution: "@emnapi/runtime@npm:1.10.0" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/953f14991d1aefb92ee6f8eb27dea725e484791a53a0cb5f47d9e0087b9a2c929ff2e92adf95af15d6ad456db6300c6b761ebf72b50a875b874a83520b3ba093 - languageName: node - linkType: hard - -"@emnapi/wasi-threads@npm:1.2.1": - version: 1.2.1 - resolution: "@emnapi/wasi-threads@npm:1.2.1" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/aix-ppc64@npm:0.28.1" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-arm64@npm:0.28.1" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-arm@npm:0.28.1" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/android-x64@npm:0.28.1" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/darwin-arm64@npm:0.28.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/darwin-x64@npm:0.28.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/freebsd-arm64@npm:0.28.1" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/freebsd-x64@npm:0.28.1" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-arm64@npm:0.28.1" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-arm@npm:0.28.1" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-ia32@npm:0.28.1" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-loong64@npm:0.28.1" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-mips64el@npm:0.28.1" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-ppc64@npm:0.28.1" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-riscv64@npm:0.28.1" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-s390x@npm:0.28.1" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/linux-x64@npm:0.28.1" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/netbsd-arm64@npm:0.28.1" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/netbsd-x64@npm:0.28.1" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openbsd-arm64@npm:0.28.1" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openbsd-x64@npm:0.28.1" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openharmony-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/openharmony-arm64@npm:0.28.1" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/sunos-x64@npm:0.28.1" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-arm64@npm:0.28.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-ia32@npm:0.28.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.28.1": - version: 0.28.1 - resolution: "@esbuild/win32-x64@npm:0.28.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@genql/runtime@npm:^2.10.0": - version: 2.10.0 - resolution: "@genql/runtime@npm:2.10.0" - dependencies: - "@types/qs": "npm:^6.9.0" - "@types/ws": "npm:^6.0.1" - graphql-query-batcher: "npm:^1.0.1" - isomorphic-unfetch: "npm:^3.0.0" - lodash: "npm:^4.17.20" - subscriptions-transport-ws: "npm:^0.9.16" - tslib: "npm:^2.0.0" - utility-types: "npm:^3.10.0" - ws: "npm:^6.1.4" - zen-observable-ts: "npm:^0.8.21" - peerDependencies: - graphql: "*" - checksum: 10c0/e2a886c2469c933681e2b0ddd6a5b7f4cb12932251ba460e3cf2db4246817da79313ea4ba9769ec7cbe53ab9c1cb81ad8fcce6a969cd241185b79398d2a4f3c6 - languageName: node - linkType: hard - -"@inquirer/ansi@npm:^2.0.7": - version: 2.0.7 - resolution: "@inquirer/ansi@npm:2.0.7" - checksum: 10c0/a574f97a899f0d9346fa26b528b3f4a9ba6dcb9172288efb6b4314d8486470ed53d2f538200f66a25b843c6e0cbf83688c6d5174a8dc6eca853b291b09609c5a - languageName: node - linkType: hard - -"@inquirer/checkbox@npm:^5.2.1": - version: 5.2.1 - resolution: "@inquirer/checkbox@npm:5.2.1" - dependencies: - "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" - "@inquirer/figures": "npm:^2.0.7" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/e3a845156c718fbbec228a0e7fd2a4f10775185615eac1f405b3a2bb2ae607dda6baf178fbd6487db0e12e5e33014536e81acefe65de57cd476a7aeaea6fb35d - languageName: node - linkType: hard - -"@inquirer/confirm@npm:^6.1.1": - version: 6.1.1 - resolution: "@inquirer/confirm@npm:6.1.1" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/4684406161c09327df830b4026f3165b31e13831276d215051586408ed434423263b15686393ce95a4b55058c1b7f9b08aa4b66f5ac930b47523fff75051d36f - languageName: node - linkType: hard - -"@inquirer/core@npm:^11.2.1": - version: 11.2.1 - resolution: "@inquirer/core@npm:11.2.1" - dependencies: - "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/figures": "npm:^2.0.7" - "@inquirer/type": "npm:^4.0.7" - cli-width: "npm:^4.1.0" - fast-wrap-ansi: "npm:^0.2.0" - mute-stream: "npm:^3.0.0" - signal-exit: "npm:^4.1.0" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/b5be386cecd9e441ac2f9d3417a6ae1c4658b3ee6cdf5dae791211400f4de158851f81fca2245e2062833716f95366b9e1717770828cb7365e756c16e822f0d2 - languageName: node - linkType: hard - -"@inquirer/editor@npm:^5.2.2": - version: 5.2.2 - resolution: "@inquirer/editor@npm:5.2.2" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/external-editor": "npm:^3.0.3" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/a75e7012aad3ccc3ec84893463ed689924515a6cbaee8e2a475769fb27c12bcf359fcdd5f62e92107fc4be88fd69444c15adf13071982efc140c7015a6604d9a - languageName: node - linkType: hard - -"@inquirer/expand@npm:^5.1.1": - version: 5.1.1 - resolution: "@inquirer/expand@npm:5.1.1" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/4de661a042147759ce351971a4f92010ab66cb76450424e7d8aec5de79b449d14e8751f54f557d0b047180bca2b76707626f4835ee46603c6200139c31b59652 - languageName: node - linkType: hard - -"@inquirer/external-editor@npm:^3.0.3": - version: 3.0.3 - resolution: "@inquirer/external-editor@npm:3.0.3" - dependencies: - chardet: "npm:^2.1.1" - iconv-lite: "npm:^0.7.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/fc24ddbff15f0874db6498c16d2fe153bb19a670d83605f480486d6ff0ea872ae2f32a548fd555797989db09850fa019a6b5e49a8d40cfee0d7deacad17edc1e - languageName: node - linkType: hard - -"@inquirer/figures@npm:^2.0.7": - version: 2.0.7 - resolution: "@inquirer/figures@npm:2.0.7" - checksum: 10c0/e0573dc9ad25fa3628d5164745e52852d8cd832a9918605b7716df2e37a0005a0aaf40b6d81cef2ca09cb708b200e61b82d1dcd17003f572577e233c19a9ec7b - languageName: node - linkType: hard - -"@inquirer/input@npm:^5.1.2": - version: 5.1.2 - resolution: "@inquirer/input@npm:5.1.2" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/9c3614f8d79fc2bec07a088858a58967a162046c9292b0c6f0c6f63396cf391181cfb54bb636f5b3dce8d23924c24ee4a0310183a493c94190e4750218f3744d - languageName: node - linkType: hard - -"@inquirer/number@npm:^4.1.1": - version: 4.1.1 - resolution: "@inquirer/number@npm:4.1.1" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/06210dd70bf89d35242af43009b5e3f76a5f07d6275a9d842bbed61536032f5f349ecba1c20012975d7b0362deb89746d5903e90f21516da10bfd8c2055829a9 - languageName: node - linkType: hard - -"@inquirer/password@npm:^5.1.1": - version: 5.1.1 - resolution: "@inquirer/password@npm:5.1.1" - dependencies: - "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/e4cb3a53b56743808f83958a8fe85738d721599690a4bc7640eaa07fb8f4765bc6f174e40c16efcc8a5958a7169667e240714a706a36e831f9c7a6822cbe8b55 - languageName: node - linkType: hard - -"@inquirer/prompts@npm:^8.5.2": - version: 8.5.2 - resolution: "@inquirer/prompts@npm:8.5.2" - dependencies: - "@inquirer/checkbox": "npm:^5.2.1" - "@inquirer/confirm": "npm:^6.1.1" - "@inquirer/editor": "npm:^5.2.2" - "@inquirer/expand": "npm:^5.1.1" - "@inquirer/input": "npm:^5.1.2" - "@inquirer/number": "npm:^4.1.1" - "@inquirer/password": "npm:^5.1.1" - "@inquirer/rawlist": "npm:^5.3.1" - "@inquirer/search": "npm:^4.2.1" - "@inquirer/select": "npm:^5.2.1" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/253b92e31c6a1f8f00a778eda8196bb53fc931723f7db4a03937f38ccd4d07c987766d4f60b9250b5d90bfe30b04747dfa73927a9f6fb886bd48091ccb202535 - languageName: node - linkType: hard - -"@inquirer/rawlist@npm:^5.3.1": - version: 5.3.1 - resolution: "@inquirer/rawlist@npm:5.3.1" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/a05eb6a16633bd7b32b3b6b2c1f645e6e28d955475b21ba7222e7e66806b1be15be9f91235b2933e8d27e04fdad81ebfb2d64fc1fc0d4b8d82dcd2718817b2b9 - languageName: node - linkType: hard - -"@inquirer/search@npm:^4.2.1": - version: 4.2.1 - resolution: "@inquirer/search@npm:4.2.1" - dependencies: - "@inquirer/core": "npm:^11.2.1" - "@inquirer/figures": "npm:^2.0.7" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/eae9b2d524aae58266f96987696be22ad62f608661d28763c413f2560094d571a39d72a40630d2470f503ad5e6e50d8c574a653cc028bf79b51104fa91f59a67 - languageName: node - linkType: hard - -"@inquirer/select@npm:^5.2.1": - version: 5.2.1 - resolution: "@inquirer/select@npm:5.2.1" - dependencies: - "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" - "@inquirer/figures": "npm:^2.0.7" - "@inquirer/type": "npm:^4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/3c6d0151b5a29111254a58eaf8b93bb4c476e68b0751526d8bff61a4bea457bdbe782890ae33977c5d2015f436596b5d32a8bb0677bfdf610f5f5998e65f5a92 - languageName: node - linkType: hard - -"@inquirer/type@npm:^4.0.7": - version: 4.0.7 - resolution: "@inquirer/type@npm:4.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/80678ac1c6e19ce309909e4a54a69adc95697ea3abc2cb92f17b1bc52f4caadbcb4003ae7339fb5a70c0d36d3bde975e1bb4450069662f41c953a0d28695bb70 - languageName: node - linkType: hard - -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 - languageName: node - linkType: hard - -"@napi-rs/wasm-runtime@npm:^1.1.4": - version: 1.1.5 - resolution: "@napi-rs/wasm-runtime@npm:1.1.5" - dependencies: - "@tybys/wasm-util": "npm:^0.10.2" - peerDependencies: - "@emnapi/core": ^1.7.1 - "@emnapi/runtime": ^1.7.1 - checksum: 10c0/727f2b6ae0e68bbe5d39aeb68aa6f183314e9f03dc50bb55a962849535b2db53ecc3fbf1554d8656a54488a608df5a2634670595cf5874dc4af2ee59f817c65d - languageName: node - linkType: hard - -"@oxc-project/types@npm:=0.133.0": - version: 0.133.0 - resolution: "@oxc-project/types@npm:0.133.0" - checksum: 10c0/70c57ba58644f7ec217b670c301801f4d06995f4ccdba6b2bd106ea3e5ee49d616573e6ef8d55530b87571a960696543687f3850e87ad173d3f88965c30cdd63 - languageName: node - linkType: hard - -"@oxlint/darwin-arm64@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/darwin-arm64@npm:0.16.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@oxlint/darwin-x64@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/darwin-x64@npm:0.16.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@oxlint/linux-arm64-gnu@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/linux-arm64-gnu@npm:0.16.12" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@oxlint/linux-arm64-musl@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/linux-arm64-musl@npm:0.16.12" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@oxlint/linux-x64-gnu@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/linux-x64-gnu@npm:0.16.12" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@oxlint/linux-x64-musl@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/linux-x64-musl@npm:0.16.12" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@oxlint/win32-arm64@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/win32-arm64@npm:0.16.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@oxlint/win32-x64@npm:0.16.12": - version: 0.16.12 - resolution: "@oxlint/win32-x64@npm:0.16.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rolldown/binding-android-arm64@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-android-arm64@npm:1.0.3" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@rolldown/binding-darwin-arm64@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-darwin-arm64@npm:1.0.3" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@rolldown/binding-darwin-x64@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-darwin-x64@npm:1.0.3" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@rolldown/binding-freebsd-x64@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-freebsd-x64@npm:1.0.3" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.3" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@rolldown/binding-linux-arm64-gnu@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.3" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@rolldown/binding-linux-arm64-musl@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.3" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@rolldown/binding-linux-ppc64-gnu@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.3" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@rolldown/binding-linux-s390x-gnu@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.3" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@rolldown/binding-linux-x64-gnu@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.3" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@rolldown/binding-linux-x64-musl@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.3" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@rolldown/binding-openharmony-arm64@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.3" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - -"@rolldown/binding-wasm32-wasi@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.3" - dependencies: - "@emnapi/core": "npm:1.10.0" - "@emnapi/runtime": "npm:1.10.0" - "@napi-rs/wasm-runtime": "npm:^1.1.4" - conditions: cpu=wasm32 - languageName: node - linkType: hard - -"@rolldown/binding-win32-arm64-msvc@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.3" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@rolldown/binding-win32-x64-msvc@npm:1.0.3": - version: 1.0.3 - resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.3" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rolldown/pluginutils@npm:^1.0.0": - version: 1.0.1 - resolution: "@rolldown/pluginutils@npm:1.0.1" - checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd - languageName: node - linkType: hard - -"@sniptt/guards@npm:^0.2.0": - version: 0.2.0 - resolution: "@sniptt/guards@npm:0.2.0" - checksum: 10c0/749bb0f550d1ddd4abdb23dc1076cba26e977659922b73d000bceeb253c09270aaced0d18e92f09d4ce2fdaae33e55f60f2142f5359bc90ba505422af0873526 - languageName: node - linkType: hard - -"@standard-schema/spec@npm:^1.1.0": - version: 1.1.0 - resolution: "@standard-schema/spec@npm:1.1.0" - checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 - languageName: node - linkType: hard - -"@tybys/wasm-util@npm:^0.10.2": - version: 0.10.2 - resolution: "@tybys/wasm-util@npm:0.10.2" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/26165bcd1fd7269f42d7fbe3de318f854a8968de8397e89fc9a423bb3e2da35a52150f382e6323b3367595beb16d9800a6f35971a5599daf76da1742ec3afc25 - languageName: node - linkType: hard - -"@types/chai@npm:^5.2.2": - version: 5.2.3 - resolution: "@types/chai@npm:5.2.3" - dependencies: - "@types/deep-eql": "npm:*" - assertion-error: "npm:^2.0.1" - checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f - languageName: node - linkType: hard - -"@types/deep-eql@npm:*": - version: 4.0.2 - resolution: "@types/deep-eql@npm:4.0.2" - checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 - languageName: node - linkType: hard - -"@types/estree@npm:^1.0.0": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 - languageName: node - linkType: hard - -"@types/node@npm:*": - version: 25.6.0 - resolution: "@types/node@npm:25.6.0" - dependencies: - undici-types: "npm:~7.19.0" - checksum: 10c0/d2d2015630ff098a201407f55f5077a20270ae4f465c739b40865cd9933b91b9c5d2b85568eadaf3db0801b91e267333ca7eb39f007428b173d1cdab4b339ac5 - languageName: node - linkType: hard - -"@types/node@npm:^24.7.2": - version: 24.12.2 - resolution: "@types/node@npm:24.12.2" - dependencies: - undici-types: "npm:~7.16.0" - checksum: 10c0/710050c42f89075c4479e4e1e4c2532486b0c41b1e2a8a13ad88641c88b88cdaea87414e19224f30028719737bd70e327edcaa184d50e86b9418941edd7eb02b - languageName: node - linkType: hard - -"@types/qs@npm:^6.9.0": - version: 6.15.0 - resolution: "@types/qs@npm:6.15.0" - checksum: 10c0/1b104cac50e655fc41d7fc1de2c2aba2908c4cf833a555b6808fb4c96752662b439238f2392a15d2590a7a6ca75dbd40e42d9378ac2be0d548ee484954363688 - languageName: node - linkType: hard - -"@types/react@npm:^19.0.0": - version: 19.2.14 - resolution: "@types/react@npm:19.2.14" - dependencies: - csstype: "npm:^3.2.2" - checksum: 10c0/7d25bf41b57719452d86d2ac0570b659210402707313a36ee612666bf11275a1c69824f8c3ee1fdca077ccfe15452f6da8f1224529b917050eb2d861e52b59b7 - languageName: node - linkType: hard - -"@types/ws@npm:^6.0.1": - version: 6.0.4 - resolution: "@types/ws@npm:6.0.4" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/fa958e64596ca9487c3ed6012834de70b47f25d971f1950cfb8e6a99cb77ff340ae82ac7627744e01b58010674ef8ede07d5a2ac29ca9ad0d67a430fcc69ae14 - languageName: node - linkType: hard - -"@vitest/expect@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/expect@npm:4.1.8" - dependencies: - "@standard-schema/spec": "npm:^1.1.0" - "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.1.8" - "@vitest/utils": "npm:4.1.8" - chai: "npm:^6.2.2" - tinyrainbow: "npm:^3.1.0" - checksum: 10c0/f7bf6c720d2427c3bd0b35472ebd84d963be7d09ecf52a0fb05e8c4d5d0c9ee164a8c28eee6360947be1b245b47faefab54560cb98e5cb678c1c1074260b9149 - languageName: node - linkType: hard - -"@vitest/mocker@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/mocker@npm:4.1.8" - dependencies: - "@vitest/spy": "npm:4.1.8" - estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.21" - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - checksum: 10c0/f8cb2b8b55dc2cba0b2399aeee528b0187042f22cbc2d50a4fd6141f5aa246ebc41700f45dd1d73eca44ddfb57dcde48b2eb317bfbb1198f5ab2cc4fd04b2ea0 - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/pretty-format@npm:4.1.8" - dependencies: - tinyrainbow: "npm:^3.1.0" - checksum: 10c0/553c456692a4b9ae13cd116c234c74b4495e0f1a0d5c51ffc3fab8ea085e3550769967e29db79bdac0cf127b1bf88b7f70bfba3dcc72be6bddf834433e30cc91 - languageName: node - linkType: hard - -"@vitest/runner@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/runner@npm:4.1.8" - dependencies: - "@vitest/utils": "npm:4.1.8" - pathe: "npm:^2.0.3" - checksum: 10c0/706808a4b7b95ea9a9268fc152dd39e15a9a754f37c7990aea167486a9094caa913dae454771ae02c18dccfabd667f8cc38eed33a1307a79d32a89878b5bcce1 - languageName: node - linkType: hard - -"@vitest/snapshot@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/snapshot@npm:4.1.8" - dependencies: - "@vitest/pretty-format": "npm:4.1.8" - "@vitest/utils": "npm:4.1.8" - magic-string: "npm:^0.30.21" - pathe: "npm:^2.0.3" - checksum: 10c0/ba4c32112491d42d24986f921c50ede5edbdb4b7eafa16c72cf8d2c9ecc44121fdb3d9365236747a9841f0d6776affc6457470fcbb082df9dbc28c24792a0c6d - languageName: node - linkType: hard - -"@vitest/spy@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/spy@npm:4.1.8" - checksum: 10c0/3c10c0325a09d16bc0e77c0be96c47c15416186e33332880c0d1dd0a51d51a866091067b81f2a2ef6fb422a7760e6cf15c04d91a0eca4d59f62e8c8401fa53fc - languageName: node - linkType: hard - -"@vitest/utils@npm:4.1.8": - version: 4.1.8 - resolution: "@vitest/utils@npm:4.1.8" - dependencies: - "@vitest/pretty-format": "npm:4.1.8" - convert-source-map: "npm:^2.0.0" - tinyrainbow: "npm:^3.1.0" - checksum: 10c0/acda9d3d640c1ebc81afb358ac30589d7d7d583af81e2d09419f0af9cbe41f3ce0b90527326943bf0da51614be5fc31afcd32259f6beb32b3417999d6ef380f3 - languageName: node - linkType: hard - -"abbrev@npm:^4.0.0": - version: 4.0.0 - resolution: "abbrev@npm:4.0.0" - checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 - languageName: node - linkType: hard - -"agent-base@npm:6": - version: 6.0.2 - resolution: "agent-base@npm:6.0.2" - dependencies: - debug: "npm:4" - checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 - languageName: node - linkType: hard - -"ansi-escapes@npm:^7.3.0": - version: 7.3.0 - resolution: "ansi-escapes@npm:7.3.0" - dependencies: - environment: "npm:^1.0.0" - checksum: 10c0/068961d99f0ef28b661a4a9f84a5d645df93ccf3b9b93816cc7d46bbe1913321d4cdf156bb842a4e1e4583b7375c631fa963efb43001c4eb7ff9ab8f78fc0679 - languageName: node - linkType: hard - -"ansi-regex@npm:^6.2.2": - version: 6.2.2 - resolution: "ansi-regex@npm:6.2.2" - checksum: 10c0/05d4acb1d2f59ab2cf4b794339c7b168890d44dda4bf0ce01152a8da0213aca207802f930442ce8cd22d7a92f44907664aac6508904e75e038fa944d2601b30f - languageName: node - linkType: hard - -"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": - version: 6.2.3 - resolution: "ansi-styles@npm:6.2.3" - checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 - languageName: node - linkType: hard - -"assertion-error@npm:^2.0.1": - version: 2.0.1 - resolution: "assertion-error@npm:2.0.1" - checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 - languageName: node - linkType: hard - -"async-function@npm:^1.0.0": - version: 1.0.0 - resolution: "async-function@npm:1.0.0" - checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 - languageName: node - linkType: hard - -"async-generator-function@npm:^1.0.0": - version: 1.0.0 - resolution: "async-generator-function@npm:1.0.0" - checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 - languageName: node - linkType: hard - -"async-limiter@npm:~1.0.0": - version: 1.0.1 - resolution: "async-limiter@npm:1.0.1" - checksum: 10c0/0693d378cfe86842a70d4c849595a0bb50dc44c11649640ca982fa90cbfc74e3cc4753b5a0847e51933f2e9c65ce8e05576e75e5e1fd963a086e673735b35969 - languageName: node - linkType: hard - -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d - languageName: node - linkType: hard - -"auto-bind@npm:^5.0.1": - version: 5.0.1 - resolution: "auto-bind@npm:5.0.1" - checksum: 10c0/a703375350ea7b6e92405d8e6bcc6dbfb84b0d7c7172b33e5788a7593929a18227999ff9aa9c32436741d06d021e6672457b1cec73287efe3fab95cff6627eaf - languageName: node - linkType: hard - -"axios@npm:^1.16.0": - version: 1.17.0 - resolution: "axios@npm:1.17.0" - dependencies: - follow-redirects: "npm:^1.16.0" - form-data: "npm:^4.0.5" - https-proxy-agent: "npm:^5.0.1" - proxy-from-env: "npm:^2.1.0" - checksum: 10c0/c4fa19ff3a3a63bde48beec03ad816b133b9a6385cccffffe172577ab18c6a70e299280d57f12c80c867fe25df41f92cb91d3a8258708a6d2be3e9e085f92650 - languageName: node - linkType: hard - -"backo2@npm:^1.0.2": - version: 1.0.2 - resolution: "backo2@npm:1.0.2" - checksum: 10c0/a9e825a6a38a6d1c4a94476eabc13d6127dfaafb0967baf104affbb67806ae26abbb58dab8d572d2cd21ef06634ff57c3ad48dff14b904e18de1474cc2f22bf3 - languageName: node - linkType: hard - -"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind-apply-helpers@npm:1.0.2" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 - languageName: node - linkType: hard - -"chai@npm:^6.2.2": - version: 6.2.2 - resolution: "chai@npm:6.2.2" - checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 - languageName: node - linkType: hard - -"chalk@npm:^5.3.0, chalk@npm:^5.6.0": - version: 5.6.2 - resolution: "chalk@npm:5.6.2" - checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 - languageName: node - linkType: hard - -"chardet@npm:^2.1.1": - version: 2.1.1 - resolution: "chardet@npm:2.1.1" - checksum: 10c0/d8391dd412338442b3de0d3a488aa9327f8bcf74b62b8723d6bd0b85c4084d50b731320e0a7c710edb1d44de75969995d2784b80e4c13b004a6c7a0db4c6e793 - languageName: node - linkType: hard - -"chokidar@npm:^4.0.0": - version: 4.0.3 - resolution: "chokidar@npm:4.0.3" - dependencies: - readdirp: "npm:^4.0.1" - checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad - languageName: node - linkType: hard - -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 - languageName: node - linkType: hard - -"cli-boxes@npm:^3.0.0": - version: 3.0.0 - resolution: "cli-boxes@npm:3.0.0" - checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 - languageName: node - linkType: hard - -"cli-cursor@npm:^4.0.0": - version: 4.0.0 - resolution: "cli-cursor@npm:4.0.0" - dependencies: - restore-cursor: "npm:^4.0.0" - checksum: 10c0/e776e8c3c6727300d0539b0d25160b2bb56aed1a63942753ba1826b012f337a6f4b7ace3548402e4f2f13b5e16bfd751be672c44b203205e7eca8be94afec42c - languageName: node - linkType: hard - -"cli-truncate@npm:^5.1.1": - version: 5.2.0 - resolution: "cli-truncate@npm:5.2.0" - dependencies: - slice-ansi: "npm:^8.0.0" - string-width: "npm:^8.2.0" - checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471 - languageName: node - linkType: hard - -"cli-width@npm:^4.1.0": - version: 4.1.0 - resolution: "cli-width@npm:4.1.0" - checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f - languageName: node - linkType: hard - -"code-excerpt@npm:^4.0.0": - version: 4.0.0 - resolution: "code-excerpt@npm:4.0.0" - dependencies: - convert-to-spaces: "npm:^2.0.1" - checksum: 10c0/b6c5a06e039cecd2ab6a0e10ee0831de8362107d1f298ca3558b5f9004cb8e0260b02dd6c07f57b9a0e346c76864d2873311ee1989809fdeb05bd5fbbadde773 - languageName: node - linkType: hard - -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: "npm:~1.0.0" - checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 - languageName: node - linkType: hard - -"commander@npm:^12.0.0": - version: 12.1.0 - resolution: "commander@npm:12.1.0" - checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 - languageName: node - linkType: hard - -"convert-source-map@npm:^2.0.0": - version: 2.0.0 - resolution: "convert-source-map@npm:2.0.0" - checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b - languageName: node - linkType: hard - -"convert-to-spaces@npm:^2.0.1": - version: 2.0.1 - resolution: "convert-to-spaces@npm:2.0.1" - checksum: 10c0/d90aa0e3b6a27f9d5265a8d32def3c5c855b3e823a9db1f26d772f8146d6b91020a2fdfd905ce8048a73fad3aaf836fef8188c67602c374405e2ae8396c4ac46 - languageName: node - linkType: hard - -"csstype@npm:^3.2.2": - version: 3.2.3 - resolution: "csstype@npm:3.2.3" - checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.1": - version: 4.4.3 - resolution: "debug@npm:4.4.3" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 - languageName: node - linkType: hard - -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 - languageName: node - linkType: hard - -"detect-libc@npm:^2.0.3": - version: 2.1.2 - resolution: "detect-libc@npm:2.1.2" - checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 - languageName: node - linkType: hard - -"dunder-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "dunder-proto@npm:1.0.1" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.2.0" - checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 - languageName: node - linkType: hard - -"emoji-regex@npm:^10.3.0": - version: 10.6.0 - resolution: "emoji-regex@npm:10.6.0" - checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"environment@npm:^1.0.0": - version: 1.1.0 - resolution: "environment@npm:1.1.0" - checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.1": - version: 1.0.1 - resolution: "es-define-property@npm:1.0.1" - checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 - languageName: node - linkType: hard - -"es-module-lexer@npm:^2.0.0": - version: 2.1.0 - resolution: "es-module-lexer@npm:2.1.0" - checksum: 10c0/93bcf2454fa72d67fe3ccd0abef8ce7933f5840a319513418a643dd8e9c6aa8f49709cecfae02ded722805dd327232d30723a807cc52e6809d6ac697c62c29fb - languageName: node - linkType: hard - -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": - version: 1.1.1 - resolution: "es-object-atoms@npm:1.1.1" - dependencies: - es-errors: "npm:^1.3.0" - checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c - languageName: node - linkType: hard - -"es-set-tostringtag@npm:^2.1.0": - version: 2.1.0 - resolution: "es-set-tostringtag@npm:2.1.0" - dependencies: - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af - languageName: node - linkType: hard - -"es-toolkit@npm:^1.39.10": - version: 1.46.0 - resolution: "es-toolkit@npm:1.46.0" - dependenciesMeta: - "@trivago/prettier-plugin-sort-imports@4.3.0": - unplugged: true - prettier-plugin-sort-re-exports@0.0.1: - unplugged: true - checksum: 10c0/d52d3602e89621d8cf8e0f74d6b574e7d5296d97ff34b57ae5331735a4296e7c88c8c28fe6e43388c3c6df84fa06179705a3125767ebcc8b76f672bd06dccfb7 - languageName: node - linkType: hard - -"esbuild@npm:^0.28.1": - version: 0.28.1 - resolution: "esbuild@npm:0.28.1" - dependencies: - "@esbuild/aix-ppc64": "npm:0.28.1" - "@esbuild/android-arm": "npm:0.28.1" - "@esbuild/android-arm64": "npm:0.28.1" - "@esbuild/android-x64": "npm:0.28.1" - "@esbuild/darwin-arm64": "npm:0.28.1" - "@esbuild/darwin-x64": "npm:0.28.1" - "@esbuild/freebsd-arm64": "npm:0.28.1" - "@esbuild/freebsd-x64": "npm:0.28.1" - "@esbuild/linux-arm": "npm:0.28.1" - "@esbuild/linux-arm64": "npm:0.28.1" - "@esbuild/linux-ia32": "npm:0.28.1" - "@esbuild/linux-loong64": "npm:0.28.1" - "@esbuild/linux-mips64el": "npm:0.28.1" - "@esbuild/linux-ppc64": "npm:0.28.1" - "@esbuild/linux-riscv64": "npm:0.28.1" - "@esbuild/linux-s390x": "npm:0.28.1" - "@esbuild/linux-x64": "npm:0.28.1" - "@esbuild/netbsd-arm64": "npm:0.28.1" - "@esbuild/netbsd-x64": "npm:0.28.1" - "@esbuild/openbsd-arm64": "npm:0.28.1" - "@esbuild/openbsd-x64": "npm:0.28.1" - "@esbuild/openharmony-arm64": "npm:0.28.1" - "@esbuild/sunos-x64": "npm:0.28.1" - "@esbuild/win32-arm64": "npm:0.28.1" - "@esbuild/win32-ia32": "npm:0.28.1" - "@esbuild/win32-x64": "npm:0.28.1" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/29cd456a79ce35ac2c7e05fe871330416b2c395c045d849653f843e51378d6e0d6e774d6dcd01b35f4e83238a29bf8decd04fcd34b3780c589a250b21e5f92bb - languageName: node - linkType: hard - -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - -"estree-walker@npm:^3.0.3": - version: 3.0.3 - resolution: "estree-walker@npm:3.0.3" - dependencies: - "@types/estree": "npm:^1.0.0" - checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d - languageName: node - linkType: hard - -"eventemitter3@npm:^3.1.0": - version: 3.1.2 - resolution: "eventemitter3@npm:3.1.2" - checksum: 10c0/c67262eccbf85848b7cc6d4abb6c6e34155e15686db2a01c57669fd0d44441a574a19d44d25948b442929e065774cbe5003d8e77eed47674fbf876ac77887793 - languageName: node - linkType: hard - -"expect-type@npm:^1.3.0": - version: 1.3.0 - resolution: "expect-type@npm:1.3.0" - checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.3 - resolution: "exponential-backoff@npm:3.1.3" - checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 - languageName: node - linkType: hard - -"fast-string-truncated-width@npm:^3.0.2": - version: 3.0.3 - resolution: "fast-string-truncated-width@npm:3.0.3" - checksum: 10c0/043b8663397d14a3880ce4f3407bcda60b40db9bbeafe62863a35d1f9c69ea17c8da3fcd72de235553e6c9cd053128cde9e24ca0d4a7463208f48db3cd23d981 - languageName: node - linkType: hard - -"fast-string-width@npm:^3.0.2": - version: 3.0.2 - resolution: "fast-string-width@npm:3.0.2" - dependencies: - fast-string-truncated-width: "npm:^3.0.2" - checksum: 10c0/c8822d175315bb353ebe782b65214ac53b13e3bf704e03b132ea7bdfa8de6a636375b3ab7a4097545393d109381c37c4f387c72a462c90b61412dbc4632f39a7 - languageName: node - linkType: hard - -"fast-wrap-ansi@npm:^0.2.0": - version: 0.2.2 - resolution: "fast-wrap-ansi@npm:0.2.2" - dependencies: - fast-string-width: "npm:^3.0.2" - checksum: 10c0/1aa7be4f7cb86f4bdb14691cb6bcc0b8df8b3b89df142ade3ae1602332dcf6f990cd750a923cd581ca0847808cb4ec1aa5afaafa7a72f849e87a2a62c98fa370 - languageName: node - linkType: hard - -"fdir@npm:^6.5.0": - version: 6.5.0 - resolution: "fdir@npm:6.5.0" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f - languageName: node - linkType: hard - -"follow-redirects@npm:^1.16.0": - version: 1.16.0 - resolution: "follow-redirects@npm:1.16.0" - peerDependenciesMeta: - debug: - optional: true - checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 - languageName: node - linkType: hard - -"form-data@npm:^4.0.5": - version: 4.0.6 - resolution: "form-data@npm:4.0.6" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - es-set-tostringtag: "npm:^2.1.0" - hasown: "npm:^2.0.4" - mime-types: "npm:^2.1.35" - checksum: 10c0/43947a77bf0ff45c6ceed789778982d47a3f3e720a74b71721174ebf3310a5f1a8be1d6b38a3ee3688e8a18a2c4273073ec0844cd37efda3eaf46d41c9c318ff - languageName: node - linkType: hard - -"fsevents@npm:~2.3.3": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"generator-function@npm:^2.0.0": - version: 2.0.1 - resolution: "generator-function@npm:2.0.1" - checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 - languageName: node - linkType: hard - -"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0": - version: 1.5.0 - resolution: "get-east-asian-width@npm:1.5.0" - checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167 - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.6": - version: 1.3.1 - resolution: "get-intrinsic@npm:1.3.1" - dependencies: - async-function: "npm:^1.0.0" - async-generator-function: "npm:^1.0.0" - call-bind-apply-helpers: "npm:^1.0.2" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - function-bind: "npm:^1.1.2" - generator-function: "npm:^2.0.0" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - math-intrinsics: "npm:^1.1.0" - checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d - languageName: node - linkType: hard - -"get-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "get-proto@npm:1.0.1" - dependencies: - dunder-proto: "npm:^1.0.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c - languageName: node - linkType: hard - -"github-connector@workspace:.": - version: 0.0.0-use.local - resolution: "github-connector@workspace:." - dependencies: - "@types/node": "npm:^24.7.2" - "@types/react": "npm:^19.0.0" - oxlint: "npm:^0.16.0" - react: "npm:^19.0.0" - react-dom: "npm:^19.0.0" - twenty-client-sdk: "npm:2.13.0" - twenty-sdk: "npm:2.13.0" - typescript: "npm:^5.9.3" - vite-tsconfig-paths: "npm:^4.2.1" - vitest: "npm:^4.0.0" - languageName: unknown - linkType: soft - -"globrex@npm:^0.1.2": - version: 0.1.2 - resolution: "globrex@npm:0.1.2" - checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 - languageName: node - linkType: hard - -"gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"graphql-query-batcher@npm:^1.0.1": - version: 1.0.1 - resolution: "graphql-query-batcher@npm:1.0.1" - checksum: 10c0/804d0f4064721a2116a16b9eac422e9233e85f4ab5b250cb8f83662725658ffde35779a8ae8211037f3dd2f9717de8cb63b394ad8057ce22171a83db2471196a - languageName: node - linkType: hard - -"graphql-sse@npm:^2.5.4": - version: 2.6.0 - resolution: "graphql-sse@npm:2.6.0" - peerDependencies: - graphql: ">=0.11 <=16" - checksum: 10c0/e05f0b5c8539d61e5ce34af8e0bb418c02bf922d6a7f9232a9abd53c77df5654684ca14f674ac771645c8fba9c37ce666c5d06f46eef54ea07e63653468065b0 - languageName: node - linkType: hard - -"graphql@npm:^16.8.1": - version: 16.13.2 - resolution: "graphql@npm:16.13.2" - checksum: 10c0/64e822a0a0e4398781e4bc9765b88d370c08261498b517add4b878038ef7be2005b6b394a79a5102b9379d57052f60bc7f23fec8f39808d101984a74772ebd9d - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e - languageName: node - linkType: hard - -"has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c - languageName: node - linkType: hard - -"hasown@npm:^2.0.2": - version: 2.0.3 - resolution: "hasown@npm:2.0.3" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/f5eb28c3fd0d3e4facd821c1eeee3836c37b70ab0b0fc532e8a39976e18fef43652415dadc52f8c7a5ff6d5ac93b7bef128789aa6f90f4e9b9a9083dce74ab38 - languageName: node - linkType: hard - -"hasown@npm:^2.0.4": - version: 2.0.4 - resolution: "hasown@npm:2.0.4" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^5.0.1": - version: 5.0.1 - resolution: "https-proxy-agent@npm:5.0.1" - dependencies: - agent-base: "npm:6" - debug: "npm:4" - checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 - languageName: node - linkType: hard - -"iconv-lite@npm:^0.7.2": - version: 0.7.2 - resolution: "iconv-lite@npm:0.7.2" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/3c228920f3bd307f56bf8363706a776f4a060eb042f131cd23855ceca962951b264d0997ab38a1ad340e1c5df8499ed26e1f4f0db6b2a2ad9befaff22f14b722 - languageName: node - linkType: hard - -"indent-string@npm:^5.0.0": - version: 5.0.0 - resolution: "indent-string@npm:5.0.0" - checksum: 10c0/8ee77b57d92e71745e133f6f444d6fa3ed503ad0e1bcd7e80c8da08b42375c07117128d670589725ed07b1978065803fa86318c309ba45415b7fe13e7f170220 - languageName: node - linkType: hard - -"ink@npm:^6.8.0": - version: 6.8.0 - resolution: "ink@npm:6.8.0" - dependencies: - "@alcalzone/ansi-tokenize": "npm:^0.2.4" - ansi-escapes: "npm:^7.3.0" - ansi-styles: "npm:^6.2.1" - auto-bind: "npm:^5.0.1" - chalk: "npm:^5.6.0" - cli-boxes: "npm:^3.0.0" - cli-cursor: "npm:^4.0.0" - cli-truncate: "npm:^5.1.1" - code-excerpt: "npm:^4.0.0" - es-toolkit: "npm:^1.39.10" - indent-string: "npm:^5.0.0" - is-in-ci: "npm:^2.0.0" - patch-console: "npm:^2.0.0" - react-reconciler: "npm:^0.33.0" - scheduler: "npm:^0.27.0" - signal-exit: "npm:^3.0.7" - slice-ansi: "npm:^8.0.0" - stack-utils: "npm:^2.0.6" - string-width: "npm:^8.1.1" - terminal-size: "npm:^4.0.1" - type-fest: "npm:^5.4.1" - widest-line: "npm:^6.0.0" - wrap-ansi: "npm:^9.0.0" - ws: "npm:^8.18.0" - yoga-layout: "npm:~3.2.1" - peerDependencies: - "@types/react": ">=19.0.0" - react: ">=19.0.0" - react-devtools-core: ">=6.1.2" - peerDependenciesMeta: - "@types/react": - optional: true - react-devtools-core: - optional: true - checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff - languageName: node - linkType: hard - -"inquirer@npm:^14.0.0": - version: 14.0.2 - resolution: "inquirer@npm:14.0.2" - dependencies: - "@inquirer/ansi": "npm:^2.0.7" - "@inquirer/core": "npm:^11.2.1" - "@inquirer/prompts": "npm:^8.5.2" - "@inquirer/type": "npm:^4.0.7" - mute-stream: "npm:^3.0.0" - run-async: "npm:^4.0.6" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/31ec80b7d599dcb19568540d694865b1da116f701b75503a23ed38189d96fca70eb1fb6ccb1dbd994f6f79d407d51dc1a94d06dcef5370644ce736792414781b - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": - version: 5.1.0 - resolution: "is-fullwidth-code-point@npm:5.1.0" - dependencies: - get-east-asian-width: "npm:^1.3.1" - checksum: 10c0/c1172c2e417fb73470c56c431851681591f6a17233603a9e6f94b7ba870b2e8a5266506490573b607fb1081318589372034aa436aec07b465c2029c0bc7f07a4 - languageName: node - linkType: hard - -"is-in-ci@npm:^2.0.0": - version: 2.0.0 - resolution: "is-in-ci@npm:2.0.0" - bin: - is-in-ci: cli.js - checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb - languageName: node - linkType: hard - -"isexe@npm:^4.0.0": - version: 4.0.0 - resolution: "isexe@npm:4.0.0" - checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce - languageName: node - linkType: hard - -"isomorphic-unfetch@npm:^3.0.0": - version: 3.1.0 - resolution: "isomorphic-unfetch@npm:3.1.0" - dependencies: - node-fetch: "npm:^2.6.1" - unfetch: "npm:^4.2.0" - checksum: 10c0/d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89 - languageName: node - linkType: hard - -"iterall@npm:^1.2.1": - version: 1.3.0 - resolution: "iterall@npm:1.3.0" - checksum: 10c0/40de624e5fe937c4c0e511981b91caea9ff2142bfc0316cccc8506eaa03aa253820cc17c5bc5f0a98706c7268a373e5ebee9af9a0c8a359730cf7c05938b57b5 - languageName: node - linkType: hard - -"jsonc-parser@npm:^3.2.0": - version: 3.3.1 - resolution: "jsonc-parser@npm:3.3.1" - checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 - languageName: node - linkType: hard - -"lightningcss-android-arm64@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-android-arm64@npm:1.32.0" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"lightningcss-darwin-arm64@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-darwin-arm64@npm:1.32.0" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"lightningcss-darwin-x64@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-darwin-x64@npm:1.32.0" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"lightningcss-freebsd-x64@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-freebsd-x64@npm:1.32.0" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"lightningcss-linux-arm-gnueabihf@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"lightningcss-linux-arm64-gnu@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"lightningcss-linux-arm64-musl@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"lightningcss-linux-x64-gnu@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"lightningcss-linux-x64-musl@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-linux-x64-musl@npm:1.32.0" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"lightningcss-win32-arm64-msvc@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"lightningcss-win32-x64-msvc@npm:1.32.0": - version: 1.32.0 - resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"lightningcss@npm:^1.32.0": - version: 1.32.0 - resolution: "lightningcss@npm:1.32.0" - dependencies: - detect-libc: "npm:^2.0.3" - lightningcss-android-arm64: "npm:1.32.0" - lightningcss-darwin-arm64: "npm:1.32.0" - lightningcss-darwin-x64: "npm:1.32.0" - lightningcss-freebsd-x64: "npm:1.32.0" - lightningcss-linux-arm-gnueabihf: "npm:1.32.0" - lightningcss-linux-arm64-gnu: "npm:1.32.0" - lightningcss-linux-arm64-musl: "npm:1.32.0" - lightningcss-linux-x64-gnu: "npm:1.32.0" - lightningcss-linux-x64-musl: "npm:1.32.0" - lightningcss-win32-arm64-msvc: "npm:1.32.0" - lightningcss-win32-x64-msvc: "npm:1.32.0" - dependenciesMeta: - lightningcss-android-arm64: - optional: true - lightningcss-darwin-arm64: - optional: true - lightningcss-darwin-x64: - optional: true - lightningcss-freebsd-x64: - optional: true - lightningcss-linux-arm-gnueabihf: - optional: true - lightningcss-linux-arm64-gnu: - optional: true - lightningcss-linux-arm64-musl: - optional: true - lightningcss-linux-x64-gnu: - optional: true - lightningcss-linux-x64-musl: - optional: true - lightningcss-win32-arm64-msvc: - optional: true - lightningcss-win32-x64-msvc: - optional: true - checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 - languageName: node - linkType: hard - -"lodash@npm:^4.17.20, lodash@npm:^4.17.21": - version: 4.18.1 - resolution: "lodash@npm:4.18.1" - checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 - languageName: node - linkType: hard - -"magic-string@npm:^0.30.21": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a - languageName: node - linkType: hard - -"math-intrinsics@npm:^1.1.0": - version: 1.1.0 - resolution: "math-intrinsics@npm:1.1.0" - checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f - languageName: node - linkType: hard - -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - -"mime-types@npm:^2.1.35": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - -"mimic-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "mimic-fn@npm:2.1.0" - checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 - languageName: node - linkType: hard - -"minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.3 - resolution: "minipass@npm:7.1.3" - checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb - languageName: node - linkType: hard - -"minizlib@npm:^3.1.0": - version: 3.1.0 - resolution: "minizlib@npm:3.1.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec - languageName: node - linkType: hard - -"ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"mute-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "mute-stream@npm:3.0.0" - checksum: 10c0/12cdb36a101694c7a6b296632e6d93a30b74401873cf7507c88861441a090c71c77a58f213acadad03bc0c8fa186639dec99d68a14497773a8744320c136e701 - languageName: node - linkType: hard - -"nanoid@npm:^3.3.12": - version: 3.3.12 - resolution: "nanoid@npm:3.3.12" - bin: - nanoid: bin/nanoid.cjs - checksum: 10c0/ba142b7b39e11e80c16dd74b0365d407880c87c1cf7e1480956981ae940ee36060fa5b6f092cd1e315184dd19244c657bd017d03327bd3c62247d691c5e8edfb - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.1": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 12.3.0 - resolution: "node-gyp@npm:12.3.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.5.4" - tinyglobby: "npm:^0.2.12" - undici: "npm:^6.25.0" - which: "npm:^6.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/9d9032b405cbe42f72a105259d9eb679376470c102df4a2dbaa51e07d59bf741dcffb85897087ea9d8318b9cabb824a8978af51508ae142f0239ae1e6a3c2329 - languageName: node - linkType: hard - -"nopt@npm:^9.0.0": - version: 9.0.0 - resolution: "nopt@npm:9.0.0" - dependencies: - abbrev: "npm:^4.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd - languageName: node - linkType: hard - -"obug@npm:^2.1.1": - version: 2.1.2 - resolution: "obug@npm:2.1.2" - checksum: 10c0/e857080ef23c018bd4ad8553238cd97008cd4ab8472b4b83eafe75c19e9b6f84ac8fe53ef7f50b515a1dbf83e6372dd0b198aece33bc716edfa70366f6f6ed5e - languageName: node - linkType: hard - -"onetime@npm:^5.1.0": - version: 5.1.2 - resolution: "onetime@npm:5.1.2" - dependencies: - mimic-fn: "npm:^2.1.0" - checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f - languageName: node - linkType: hard - -"oxlint@npm:^0.16.0": - version: 0.16.12 - resolution: "oxlint@npm:0.16.12" - dependencies: - "@oxlint/darwin-arm64": "npm:0.16.12" - "@oxlint/darwin-x64": "npm:0.16.12" - "@oxlint/linux-arm64-gnu": "npm:0.16.12" - "@oxlint/linux-arm64-musl": "npm:0.16.12" - "@oxlint/linux-x64-gnu": "npm:0.16.12" - "@oxlint/linux-x64-musl": "npm:0.16.12" - "@oxlint/win32-arm64": "npm:0.16.12" - "@oxlint/win32-x64": "npm:0.16.12" - dependenciesMeta: - "@oxlint/darwin-arm64": - optional: true - "@oxlint/darwin-x64": - optional: true - "@oxlint/linux-arm64-gnu": - optional: true - "@oxlint/linux-arm64-musl": - optional: true - "@oxlint/linux-x64-gnu": - optional: true - "@oxlint/linux-x64-musl": - optional: true - "@oxlint/win32-arm64": - optional: true - "@oxlint/win32-x64": - optional: true - bin: - oxc_language_server: bin/oxc_language_server - oxlint: bin/oxlint - checksum: 10c0/209c3484039c4f1fdd340689e81be93a70fdf74e8c111731e70d81580d223b0029439107c95fa4118cec3c9b03ba6c56624d3dcb8cdc79af222da3561122b3e7 - languageName: node - linkType: hard - -"patch-console@npm:^2.0.0": - version: 2.0.0 - resolution: "patch-console@npm:2.0.0" - checksum: 10c0/486602591a0af7af8d4c76d8eea42cad32b6de7200488819c6383c75e43733ca7bdc80e30f2e68ce05f06a1607cce1683a1706c6672ca27dada1921b366e8f1c - languageName: node - linkType: hard - -"pathe@npm:^2.0.3": - version: 2.0.3 - resolution: "pathe@npm:2.0.3" - checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 - languageName: node - linkType: hard - -"picocolors@npm:^1.1.1": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 - languageName: node - linkType: hard - -"picomatch@npm:^4.0.3, picomatch@npm:^4.0.4": - version: 4.0.4 - resolution: "picomatch@npm:4.0.4" - checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 - languageName: node - linkType: hard - -"postcss@npm:^8.5.15": - version: 8.5.15 - resolution: "postcss@npm:8.5.15" - dependencies: - nanoid: "npm:^3.3.12" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10c0/7f2e63ae22fbe43aace1bf652bd99da4e90737c64194d49e51ddc9cd0f9e51ff2861a7d734379b494deffa03a880a5c65eec70bc29ee9ebaa7136dde3eee8f31 - languageName: node - linkType: hard - -"preact@npm:^10.28.3": - version: 10.29.1 - resolution: "preact@npm:10.29.1" - checksum: 10c0/55da3994d0d9ebcc26476552c6571f8be1150cccdafe842aad40eff9abc84c867555f6d01db386fc63ac4cbe9f9da245002468bb24df0642365162d8f1b545e2 - languageName: node - linkType: hard - -"prettier@npm:^3.8.3": - version: 3.8.4 - resolution: "prettier@npm:3.8.4" - bin: - prettier: bin/prettier.cjs - checksum: 10c0/b90a0cbe75b88ac0af9c13fe0f359bd19926fabccd88483227b21f71f0c1cc42da056fc1ac3a361e665577c568371d5ccfb2c62c31c8a1186f8d1bd531a063e9 - languageName: node - linkType: hard - -"proc-log@npm:^6.0.0": - version: 6.1.0 - resolution: "proc-log@npm:6.1.0" - checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 - languageName: node - linkType: hard - -"proxy-from-env@npm:^2.1.0": - version: 2.1.0 - resolution: "proxy-from-env@npm:2.1.0" - checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 - languageName: node - linkType: hard - -"react-dom@npm:^19.0.0": - version: 19.2.5 - resolution: "react-dom@npm:19.2.5" - dependencies: - scheduler: "npm:^0.27.0" - peerDependencies: - react: ^19.2.5 - checksum: 10c0/8067606e9f58e4c2e8cb5f09570217dbc71c4843ebcaa20ae2085912d3e3a351f17d8f7c1713313cdda7f272840c8c34ff6c860fcb840862071bceea218e0c63 - languageName: node - linkType: hard - -"react-dom@npm:^19.2.0": - version: 19.2.7 - resolution: "react-dom@npm:19.2.7" - dependencies: - scheduler: "npm:^0.27.0" - peerDependencies: - react: ^19.2.7 - checksum: 10c0/970ff600f6e80d47d39e2f226f12f226173b3cba3382efc97c5f0cd663de9af38c7a4c11c213fb936094faeac83060d660247accaa96b752180d5b951b9cfecb - languageName: node - linkType: hard - -"react-reconciler@npm:^0.33.0": - version: 0.33.0 - resolution: "react-reconciler@npm:0.33.0" - dependencies: - scheduler: "npm:^0.27.0" - peerDependencies: - react: ^19.2.0 - checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a - languageName: node - linkType: hard - -"react@npm:^19.0.0": - version: 19.2.5 - resolution: "react@npm:19.2.5" - checksum: 10c0/4b5f231dbef92886f602533c9ce3bde04d99f0e71dfb5d794c43e02726efaad0421c08688f75fc98a6d6e1dc017372e1af7abbfecdc86a79968f461675931a7a - languageName: node - linkType: hard - -"react@npm:^19.2.0": - version: 19.2.7 - resolution: "react@npm:19.2.7" - checksum: 10c0/0bd0e2f1bbd4ba97561c6597bf8a5fec05e6476fe61e165c1065598d16668efc6715205599c94d3ddd49d36cb0f21cbf1b9bcc18ee840b805ce222c3e8d558ac - languageName: node - linkType: hard - -"readdirp@npm:^4.0.1": - version: 4.1.2 - resolution: "readdirp@npm:4.1.2" - checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 - languageName: node - linkType: hard - -"restore-cursor@npm:^4.0.0": - version: 4.0.0 - resolution: "restore-cursor@npm:4.0.0" - dependencies: - onetime: "npm:^5.1.0" - signal-exit: "npm:^3.0.2" - checksum: 10c0/6f7da8c5e422ac26aa38354870b1afac09963572cf2879443540449068cb43476e9cbccf6f8de3e0171e0d6f7f533c2bc1a0a008003c9a525bbc098e89041318 - languageName: node - linkType: hard - -"rolldown@npm:1.0.3": - version: 1.0.3 - resolution: "rolldown@npm:1.0.3" - dependencies: - "@oxc-project/types": "npm:=0.133.0" - "@rolldown/binding-android-arm64": "npm:1.0.3" - "@rolldown/binding-darwin-arm64": "npm:1.0.3" - "@rolldown/binding-darwin-x64": "npm:1.0.3" - "@rolldown/binding-freebsd-x64": "npm:1.0.3" - "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.3" - "@rolldown/binding-linux-arm64-gnu": "npm:1.0.3" - "@rolldown/binding-linux-arm64-musl": "npm:1.0.3" - "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.3" - "@rolldown/binding-linux-s390x-gnu": "npm:1.0.3" - "@rolldown/binding-linux-x64-gnu": "npm:1.0.3" - "@rolldown/binding-linux-x64-musl": "npm:1.0.3" - "@rolldown/binding-openharmony-arm64": "npm:1.0.3" - "@rolldown/binding-wasm32-wasi": "npm:1.0.3" - "@rolldown/binding-win32-arm64-msvc": "npm:1.0.3" - "@rolldown/binding-win32-x64-msvc": "npm:1.0.3" - "@rolldown/pluginutils": "npm:^1.0.0" - dependenciesMeta: - "@rolldown/binding-android-arm64": - optional: true - "@rolldown/binding-darwin-arm64": - optional: true - "@rolldown/binding-darwin-x64": - optional: true - "@rolldown/binding-freebsd-x64": - optional: true - "@rolldown/binding-linux-arm-gnueabihf": - optional: true - "@rolldown/binding-linux-arm64-gnu": - optional: true - "@rolldown/binding-linux-arm64-musl": - optional: true - "@rolldown/binding-linux-ppc64-gnu": - optional: true - "@rolldown/binding-linux-s390x-gnu": - optional: true - "@rolldown/binding-linux-x64-gnu": - optional: true - "@rolldown/binding-linux-x64-musl": - optional: true - "@rolldown/binding-openharmony-arm64": - optional: true - "@rolldown/binding-wasm32-wasi": - optional: true - "@rolldown/binding-win32-arm64-msvc": - optional: true - "@rolldown/binding-win32-x64-msvc": - optional: true - bin: - rolldown: ./bin/cli.mjs - checksum: 10c0/5f9dd47b7abf203b16bc600db68542f245e974c800e59ff50b76157d1dada1403657690435b036fabca88e93d13a67c31abe5cfaa6f61ce33717f61720204cdf - languageName: node - linkType: hard - -"run-async@npm:^4.0.6": - version: 4.0.6 - resolution: "run-async@npm:4.0.6" - checksum: 10c0/3e512c689d356238a06a59839deddeb09aec23bc66f780fe970fcf12b64bfc00c6880e9530ea22b8cf88a927145561f5a43343d8be87166e849ec0daaa3d4cf4 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 - languageName: node - linkType: hard - -"scheduler@npm:^0.27.0": - version: 0.27.0 - resolution: "scheduler@npm:0.27.0" - checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 - languageName: node - linkType: hard - -"semver@npm:^7.3.5": - version: 7.7.4 - resolution: "semver@npm:7.7.4" - bin: - semver: bin/semver.js - checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 - languageName: node - linkType: hard - -"siginfo@npm:^2.0.0": - version: 2.0.0 - resolution: "siginfo@npm:2.0.0" - checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 - languageName: node - linkType: hard - -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": - version: 3.0.7 - resolution: "signal-exit@npm:3.0.7" - checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 - languageName: node - linkType: hard - -"signal-exit@npm:^4.1.0": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 - languageName: node - linkType: hard - -"slice-ansi@npm:^8.0.0": - version: 8.0.0 - resolution: "slice-ansi@npm:8.0.0" - dependencies: - ansi-styles: "npm:^6.2.3" - is-fullwidth-code-point: "npm:^5.1.0" - checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a - languageName: node - linkType: hard - -"source-map-js@npm:^1.2.1": - version: 1.2.1 - resolution: "source-map-js@npm:1.2.1" - checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf - languageName: node - linkType: hard - -"stack-utils@npm:^2.0.6": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a - languageName: node - linkType: hard - -"stackback@npm:0.0.2": - version: 0.0.2 - resolution: "stackback@npm:0.0.2" - checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 - languageName: node - linkType: hard - -"std-env@npm:^4.0.0-rc.1": - version: 4.1.0 - resolution: "std-env@npm:4.1.0" - checksum: 10c0/2e14b6b490db34cb969a48d9cf7c35bca4a47653914aac2814221baae7b867a5b15940d133625c391621971f98cd2266a5dc7036669960e883f1081db2a56558 - languageName: node - linkType: hard - -"string-width@npm:^7.0.0": - version: 7.2.0 - resolution: "string-width@npm:7.2.0" - dependencies: - emoji-regex: "npm:^10.3.0" - get-east-asian-width: "npm:^1.0.0" - strip-ansi: "npm:^7.1.0" - checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 - languageName: node - linkType: hard - -"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0": - version: 8.2.0 - resolution: "string-width@npm:8.2.0" - dependencies: - get-east-asian-width: "npm:^1.5.0" - strip-ansi: "npm:^7.1.2" - checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": - version: 7.2.0 - resolution: "strip-ansi@npm:7.2.0" - dependencies: - ansi-regex: "npm:^6.2.2" - checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 - languageName: node - linkType: hard - -"subscriptions-transport-ws@npm:^0.9.16": - version: 0.9.19 - resolution: "subscriptions-transport-ws@npm:0.9.19" - dependencies: - backo2: "npm:^1.0.2" - eventemitter3: "npm:^3.1.0" - iterall: "npm:^1.2.1" - symbol-observable: "npm:^1.0.4" - ws: "npm:^5.2.0 || ^6.0.0 || ^7.0.0" - peerDependencies: - graphql: ">=0.10.0" - checksum: 10c0/6f2ade56865f0ba291d3ff82c79781b051c2374873bac853286fedfdbc05001b8c4018ab7cba44af667ead7f573e48d18892d58a8f9ca8d90dfb4bff5c125045 - languageName: node - linkType: hard - -"symbol-observable@npm:^1.0.4": - version: 1.2.0 - resolution: "symbol-observable@npm:1.2.0" - checksum: 10c0/009fee50798ef80ed4b8195048288f108b03de162db07493f2e1fd993b33fafa72d659e832b584da5a2427daa78e5a738fb2a9ab027ee9454252e0bedbcd1fdc - languageName: node - linkType: hard - -"tagged-tag@npm:^1.0.0": - version: 1.0.0 - resolution: "tagged-tag@npm:1.0.0" - checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1 - languageName: node - linkType: hard - -"tar@npm:^7.5.4": - version: 7.5.16 - resolution: "tar@npm:7.5.16" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c - languageName: node - linkType: hard - -"terminal-size@npm:^4.0.1": - version: 4.0.1 - resolution: "terminal-size@npm:4.0.1" - checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8 - languageName: node - linkType: hard - -"tinybench@npm:^2.9.0": - version: 2.9.0 - resolution: "tinybench@npm:2.9.0" - checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c - languageName: node - linkType: hard - -"tinyexec@npm:^1.0.2": - version: 1.2.4 - resolution: "tinyexec@npm:1.2.4" - checksum: 10c0/153b8db6b080194b558ff145b9cffc36b80a6e07babd644dcfbe49c807eee668c876049d28bdee90b96304476f883352f2dad91b3f86bc23832532f4363e66ff - languageName: node - linkType: hard - -"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": - version: 0.2.16 - resolution: "tinyglobby@npm:0.2.16" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.4" - checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b - languageName: node - linkType: hard - -"tinyglobby@npm:^0.2.17": - version: 0.2.17 - resolution: "tinyglobby@npm:0.2.17" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.4" - checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c - languageName: node - linkType: hard - -"tinyrainbow@npm:^3.1.0": - version: 3.1.0 - resolution: "tinyrainbow@npm:3.1.0" - checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511 - languageName: node - linkType: hard - -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 - languageName: node - linkType: hard - -"tsconfck@npm:^3.0.3": - version: 3.1.6 - resolution: "tsconfck@npm:3.1.6" - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - bin: - tsconfck: bin/tsconfck.js - checksum: 10c0/269c3c513540be44844117bb9b9258fe6f8aeab026d32aeebf458d5299125f330711429dbb556dbf125a0bc25f4a81e6c24ac96de2740badd295c3fb400f66c4 - languageName: node - linkType: hard - -"tslib@npm:^1.9.3": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 - languageName: node - linkType: hard - -"tslib@npm:^2.0.0, tslib@npm:^2.4.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 - languageName: node - linkType: hard - -"twenty-client-sdk@npm:2.13.0": - version: 2.13.0 - resolution: "twenty-client-sdk@npm:2.13.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 - languageName: node - linkType: hard - -"twenty-sdk@npm:2.13.0": - version: 2.13.0 - resolution: "twenty-sdk@npm:2.13.0" - dependencies: - "@sniptt/guards": "npm:^0.2.0" - axios: "npm:^1.16.0" - chalk: "npm:^5.3.0" - chokidar: "npm:^4.0.0" - commander: "npm:^12.0.0" - esbuild: "npm:^0.28.1" - graphql: "npm:^16.8.1" - graphql-sse: "npm:^2.5.4" - ink: "npm:^6.8.0" - inquirer: "npm:^14.0.0" - jsonc-parser: "npm:^3.2.0" - preact: "npm:^10.28.3" - react: "npm:^19.2.0" - react-dom: "npm:^19.2.0" - tinyglobby: "npm:^0.2.15" - twenty-client-sdk: "npm:2.13.0" - typescript: "npm:^5.9.3" - uuid: "npm:^13.0.2" - bin: - twenty: dist/cli.cjs - checksum: 10c0/51c350abe5d344fcad3c961a77632fd3cb2d91e357d0562b3eb02f05f66dbc41221c5463d0f91c022316a16af03a05ad43f038d9534c0ae31c060008d0e48672 - languageName: node - linkType: hard - -"type-fest@npm:^5.4.1": - version: 5.6.0 - resolution: "type-fest@npm:5.6.0" - dependencies: - tagged-tag: "npm:^1.0.0" - checksum: 10c0/5468a8ffda7f3904e6f7bbd8069eb8b6dd4bd9156e206df7a01d09a73e28cd1afedf74ead9d0fc12841c8c90074194859feca240511c50800962fde1bd9ddcbc - languageName: node - linkType: hard - -"typescript@npm:^5.9.3": - version: 5.9.3 - resolution: "typescript@npm:5.9.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": - version: 5.9.3 - resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 - languageName: node - linkType: hard - -"undici-types@npm:~7.16.0": - version: 7.16.0 - resolution: "undici-types@npm:7.16.0" - checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a - languageName: node - linkType: hard - -"undici-types@npm:~7.19.0": - version: 7.19.2 - resolution: "undici-types@npm:7.19.2" - checksum: 10c0/7159f10546f9f6c47d36776bb1bbf8671e87c1e587a6fee84ae1f111ae8de4f914efa8ca0dfcd224f4f4a9dfc3f6028f627ccb5ddaccf82d7fd54671b89fac3e - languageName: node - linkType: hard - -"undici@npm:^6.25.0": - version: 6.27.0 - resolution: "undici@npm:6.27.0" - checksum: 10c0/f88c3dae3957dbf9d93cb481440aced317bd3c4941b5914fea5efba516d51138988cdb5c76006f0bb1337e41d56c3443351055d492e73af2428521c37ba2a76f - languageName: node - linkType: hard - -"unfetch@npm:^4.2.0": - version: 4.2.0 - resolution: "unfetch@npm:4.2.0" - checksum: 10c0/a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b - languageName: node - linkType: hard - -"utility-types@npm:^3.10.0": - version: 3.11.0 - resolution: "utility-types@npm:3.11.0" - checksum: 10c0/2f1580137b0c3e6cf5405f37aaa8f5249961a76d26f1ca8efc0ff49a2fc0e0b2db56de8e521a174d075758e0c7eb3e590edec0832eb44478b958f09914920f19 - languageName: node - linkType: hard - -"uuid@npm:^13.0.2": - version: 13.0.2 - resolution: "uuid@npm:13.0.2" - bin: - uuid: dist-node/bin/uuid - checksum: 10c0/32c7ee84fa7c7966cc09b3a1514a752a8e2f609f15c00033fbaedc0255d577d1877b839f11ee537f37e587bbc620bbb98c3b3a1482931b8ed47d79497cd7a7cd - languageName: node - linkType: hard - -"vite-tsconfig-paths@npm:^4.2.1": - version: 4.3.2 - resolution: "vite-tsconfig-paths@npm:4.3.2" - dependencies: - debug: "npm:^4.1.1" - globrex: "npm:^0.1.2" - tsconfck: "npm:^3.0.3" - peerDependencies: - vite: "*" - peerDependenciesMeta: - vite: - optional: true - checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 - languageName: node - linkType: hard - -"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": - version: 8.0.16 - resolution: "vite@npm:8.0.16" - dependencies: - fsevents: "npm:~2.3.3" - lightningcss: "npm:^1.32.0" - picomatch: "npm:^4.0.4" - postcss: "npm:^8.5.15" - rolldown: "npm:1.0.3" - tinyglobby: "npm:^0.2.17" - peerDependencies: - "@types/node": ^20.19.0 || >=22.12.0 - "@vitejs/devtools": ^0.1.18 - esbuild: ^0.27.0 || ^0.28.0 - jiti: ">=1.21.0" - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: ">=0.54.8" - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - "@vitejs/devtools": - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - bin: - vite: bin/vite.js - checksum: 10c0/d75be3fbe2f63e6a8145325970338afaf0dd4d96ba9175c13f9a286fd5f95afc489401b693e4fa6c0899a4dd0e137be91cdf9401a40a635563911ad5036e3467 - languageName: node - linkType: hard - -"vitest@npm:^4.0.0": - version: 4.1.8 - resolution: "vitest@npm:4.1.8" - dependencies: - "@vitest/expect": "npm:4.1.8" - "@vitest/mocker": "npm:4.1.8" - "@vitest/pretty-format": "npm:4.1.8" - "@vitest/runner": "npm:4.1.8" - "@vitest/snapshot": "npm:4.1.8" - "@vitest/spy": "npm:4.1.8" - "@vitest/utils": "npm:4.1.8" - es-module-lexer: "npm:^2.0.0" - expect-type: "npm:^1.3.0" - magic-string: "npm:^0.30.21" - obug: "npm:^2.1.1" - pathe: "npm:^2.0.3" - picomatch: "npm:^4.0.3" - std-env: "npm:^4.0.0-rc.1" - tinybench: "npm:^2.9.0" - tinyexec: "npm:^1.0.2" - tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.1.0" - vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" - why-is-node-running: "npm:^2.3.0" - peerDependencies: - "@edge-runtime/vm": "*" - "@opentelemetry/api": ^1.9.0 - "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.1.8 - "@vitest/browser-preview": 4.1.8 - "@vitest/browser-webdriverio": 4.1.8 - "@vitest/coverage-istanbul": 4.1.8 - "@vitest/coverage-v8": 4.1.8 - "@vitest/ui": 4.1.8 - happy-dom: "*" - jsdom: "*" - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - "@edge-runtime/vm": - optional: true - "@opentelemetry/api": - optional: true - "@types/node": - optional: true - "@vitest/browser-playwright": - optional: true - "@vitest/browser-preview": - optional: true - "@vitest/browser-webdriverio": - optional: true - "@vitest/coverage-istanbul": - optional: true - "@vitest/coverage-v8": - optional: true - "@vitest/ui": - optional: true - happy-dom: - optional: true - jsdom: - optional: true - vite: - optional: false - bin: - vitest: vitest.mjs - checksum: 10c0/f459c500f8818c7a2318cd23228b4e5c0b5efb25bf8e5cb7f116c6d26e51482b2f800a8bb19837c0b5f0d05c51519edbf502bc8ceb5bd86868e8facf1d2c498e - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 - languageName: node - linkType: hard - -"which@npm:^6.0.0": - version: 6.0.1 - resolution: "which@npm:6.0.1" - dependencies: - isexe: "npm:^4.0.0" - bin: - node-which: bin/which.js - checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 - languageName: node - linkType: hard - -"why-is-node-running@npm:^2.3.0": - version: 2.3.0 - resolution: "why-is-node-running@npm:2.3.0" - dependencies: - siginfo: "npm:^2.0.0" - stackback: "npm:0.0.2" - bin: - why-is-node-running: cli.js - checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 - languageName: node - linkType: hard - -"widest-line@npm:^6.0.0": - version: 6.0.0 - resolution: "widest-line@npm:6.0.0" - dependencies: - string-width: "npm:^8.1.0" - checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7 - languageName: node - linkType: hard - -"wrap-ansi@npm:^9.0.0": - version: 9.0.2 - resolution: "wrap-ansi@npm:9.0.2" - dependencies: - ansi-styles: "npm:^6.2.1" - string-width: "npm:^7.0.0" - strip-ansi: "npm:^7.1.0" - checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c - languageName: node - linkType: hard - -"ws@npm:^5.2.0 || ^6.0.0 || ^7.0.0": - version: 7.5.11 - resolution: "ws@npm:7.5.11" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/7972670b676fb1ccba73b0899ca3c2e04e8c2075629c2614cced7f556536f96a672bbf4619fc5a06c8b8720bb839a47ca88c69c95dc14c9c61a99fbecba1c866 - languageName: node - linkType: hard - -"ws@npm:^6.1.4": - version: 6.2.4 - resolution: "ws@npm:6.2.4" - dependencies: - async-limiter: "npm:~1.0.0" - checksum: 10c0/5c2b9474164f9cb68c7776a1d10b0461c186f3a69bffb1028fca33eba5ab7206a09173fb0b311d6c5a81c8cf148406f8deb0b7d899542ab8ca67407d99717dad - languageName: node - linkType: hard - -"ws@npm:^8.18.0": - version: 8.21.0 - resolution: "ws@npm:8.21.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/ef4a243476283fc49bc7550966c4af4aa0eef56273837211e700de3b664e08604a760cdddcb5ba43c049140e74ccfec5b0ee0bb439e08c2adf9138902fdde5f9 - languageName: node - linkType: hard - -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 - languageName: node - linkType: hard - -"yoga-layout@npm:~3.2.1": - version: 3.2.1 - resolution: "yoga-layout@npm:3.2.1" - checksum: 10c0/9001e51be993c85e03757e5a04a2b61b8b30c9e5a7865d0156ca87a6431a3b717d51eb4990bfe588189fcfeac688dd9c3de707bbd50d1c344a84e63974cc54a8 - languageName: node - linkType: hard - -"zen-observable-ts@npm:^0.8.21": - version: 0.8.21 - resolution: "zen-observable-ts@npm:0.8.21" - dependencies: - tslib: "npm:^1.9.3" - zen-observable: "npm:^0.8.0" - checksum: 10c0/fe4a02f862b5f7e8ae0f86230c37b84c7d5611f5c206981afb4043e732d04cf7067a6cbe1ba82d20f18b735a3387937195a12542158a631d308ae3959a1d93c4 - languageName: node - linkType: hard - -"zen-observable@npm:^0.8.0": - version: 0.8.15 - resolution: "zen-observable@npm:0.8.15" - checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8 - languageName: node - linkType: hard diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/package.json b/packages/twenty-apps/internal/twenty-meeting-bot/package.json index fdcf2acab1..b8cd1e9e3d 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/package.json +++ b/packages/twenty-apps/internal/twenty-meeting-bot/package.json @@ -13,6 +13,7 @@ "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:unit": "vitest run --config vitest.unit.config.ts", "test:unit:watch": "vitest --config vitest.unit.config.ts", @@ -26,6 +27,7 @@ "@emotion/styled": "^11.14.0", "@types/node": "^24.7.2", "@types/react": "^19.0.0", + "@typescript/native-preview": "^7.0.0-dev.20260116.1", "oxlint": "^0.16.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/complete-call-recording-ingestion.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/complete-call-recording-ingestion.util.ts index 4a48a91e47..22d3da8299 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/complete-call-recording-ingestion.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/complete-call-recording-ingestion.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/create-call-recording.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/create-call-recording.util.ts index 8b653bc104..c8ed9a95f5 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/create-call-recording.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/create-call-recording.util.ts @@ -1,8 +1,8 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; -import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; -import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; +import { type CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; +import { type CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; export type ScheduledCallRecordingFields = { title: string | null; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts index 18cfb33172..f7d6b7ec8f 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts @@ -1,5 +1,5 @@ import { isString, isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { TWENTY_PAGE_SIZE } from 'src/logic-functions/constants/twenty-page-size'; import { type CalendarEventRecord } from 'src/logic-functions/types/calendar-event-record.type'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts index a6144f712b..d703198a5f 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type CalendarEventRecord } from 'src/logic-functions/types/calendar-event-record.type'; import { fetchCalendarEventsByFilter } from 'src/logic-functions/data/fetch-calendar-events-by-filter.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts index a5a70b9c46..aa5609cf8c 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type CalendarEventRecord } from 'src/logic-functions/types/calendar-event-record.type'; import { fetchCalendarEventsByFilter } from 'src/logic-functions/data/fetch-calendar-events-by-filter.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts index bfbbe14bbf..b07ed738e7 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type CallRecordingRecord } from 'src/logic-functions/types/call-recording-record.type'; import { findCallRecordingsByFilter } from 'src/logic-functions/data/find-call-recordings-by-filter.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-filter.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-filter.util.ts index c17c2f1a7c..98cc6994c0 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-filter.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-filter.util.ts @@ -1,5 +1,5 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { TWENTY_PAGE_SIZE } from 'src/logic-functions/constants/twenty-page-size'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-ids.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-ids.util.ts index 8559795859..5ef2493faf 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-ids.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-call-recordings-by-ids.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type CallRecordingRecord } from 'src/logic-functions/types/call-recording-record.type'; import { findCallRecordingsByFilter } from 'src/logic-functions/data/find-call-recordings-by-filter.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts index ef8571624a..5bd855c6f4 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/update-call-recording.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/update-call-recording.util.ts index bbe8bd0012..6519c5a15f 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/update-call-recording.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/data/update-call-recording.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { type CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/cancel-call-recording-request.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/cancel-call-recording-request.util.ts index 700421d9f0..8184955a53 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/cancel-call-recording-request.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/cancel-call-recording-request.util.ts @@ -1,5 +1,5 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { type CallRecordingRecord } from 'src/logic-functions/types/call-recording-record.type'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/complete-and-charge-call-recording.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/complete-and-charge-call-recording.util.ts index ebe3a7c6a1..1c1dff964b 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/complete-and-charge-call-recording.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { completeCallRecordingIngestion } from 'src/logic-functions/data/complete-call-recording-ingestion.util'; import { chargeCompletedCallRecording } from 'src/logic-functions/flows/charge-completed-call-recording.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/converge-diverged-call-recordings.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/converge-diverged-call-recordings.util.ts index 5f0576fd9c..f1082f9f42 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/converge-diverged-call-recordings.util.ts @@ -1,5 +1,5 @@ import { isNonEmptyArray, isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/ensure-meeting-bot.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/ensure-meeting-bot.util.ts index 286d1aad64..c9b9fc0e2e 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/ensure-meeting-bot.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/ensure-meeting-bot.util.ts @@ -1,5 +1,5 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { type MeetingRecording } from 'src/logic-functions/types/meeting-recording.type'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/handle-recall-webhook.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/handle-recall-webhook.util.ts index b75d0c6023..4b8719893b 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/handle-recall-webhook.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/handle-recall-webhook.util.ts @@ -1,5 +1,5 @@ import { isNonEmptyArray, isNull, isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts index 6dbbe8af75..a445a00a2a 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts @@ -1,5 +1,5 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type CalendarEventRecord } from 'src/logic-functions/types/calendar-event-record.type'; import { ensureMeetingBot } from 'src/logic-functions/flows/ensure-meeting-bot.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/persist-call-recording-progress.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/persist-call-recording-progress.util.ts index 6a1a620786..336c1d9226 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/persist-call-recording-progress.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/persist-call-recording-progress.util.ts @@ -1,4 +1,4 @@ -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type'; import { completeAndChargeCallRecording } from 'src/logic-functions/flows/complete-and-charge-call-recording.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reap-orphaned-meeting-bots.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reap-orphaned-meeting-bots.util.ts index a49a0ec150..099a5fc6c4 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reap-orphaned-meeting-bots.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reap-orphaned-meeting-bots.util.ts @@ -1,5 +1,5 @@ import { isNull, isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { APPLICATION_ID_ENV_VAR_NAME } from 'src/logic-functions/constants/application-id-env-var-name'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reconcile-meeting-bot.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reconcile-meeting-bot.util.ts index 363cdd931a..615c12a014 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reconcile-meeting-bot.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reconcile-meeting-bot.util.ts @@ -1,5 +1,5 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status'; import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reschedule-call-recording-bot.util.ts b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reschedule-call-recording-bot.util.ts index 693670ece9..c6a564cc69 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/src/logic-functions/flows/reschedule-call-recording-bot.util.ts @@ -1,5 +1,5 @@ import { isUndefined } from '@sniptt/guards'; -import { CoreApiClient } from 'twenty-client-sdk/core'; +import { type CoreApiClient } from 'twenty-client-sdk/core'; import { type MeetingRecording } from 'src/logic-functions/types/meeting-recording.type'; import { buildRecallBotMetadata } from 'src/logic-functions/domain/build-recall-bot-metadata.util'; diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/tsconfig.json b/packages/twenty-apps/internal/twenty-meeting-bot/tsconfig.json index d574c8c810..f71645f97a 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/tsconfig.json +++ b/packages/twenty-apps/internal/twenty-meeting-bot/tsconfig.json @@ -6,7 +6,7 @@ "outDir": "./dist", "rootDir": ".", "jsx": "react-jsx", - "moduleResolution": "node", + "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/vitest.config.ts b/packages/twenty-apps/internal/twenty-meeting-bot/vitest.config.ts index 8f359b3659..055af77dbe 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/vitest.config.ts +++ b/packages/twenty-apps/internal/twenty-meeting-bot/vitest.config.ts @@ -1,18 +1,14 @@ -import { loadEnv } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; import { defineConfig } from 'vitest/config'; -const fileEnv = loadEnv('test', process.cwd(), 'TWENTY_'); - -const TWENTY_API_URL = - process.env.TWENTY_API_URL ?? fileEnv.TWENTY_API_URL ?? 'http://localhost:2020'; -const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? fileEnv.TWENTY_API_KEY; +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TWENTY_API_KEY = + process.env.TWENTY_API_KEY ?? + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjQ4OTE0NDk2MDAsImp0aSI6IjIwMjAyMDIwLWY0MDEtNGQ4YS1hNzMxLTY0ZDAwN2MyN2JhZCJ9.bfQjfyN0NEtTCLE_xPyNcwonDzlSXFoP8kdCQTdnuDc'; // Make env vars available to globalSetup (test.env only applies to workers) process.env.TWENTY_API_URL = TWENTY_API_URL; -if (TWENTY_API_KEY) { - process.env.TWENTY_API_KEY = TWENTY_API_KEY; -} +process.env.TWENTY_API_KEY = TWENTY_API_KEY; export default defineConfig({ plugins: [ @@ -29,7 +25,7 @@ export default defineConfig({ globalSetup: ['src/__tests__/global-setup.ts'], env: { TWENTY_API_URL, - ...(TWENTY_API_KEY ? { TWENTY_API_KEY } : {}), + TWENTY_API_KEY, }, }, }); diff --git a/packages/twenty-apps/internal/twenty-meeting-bot/yarn.lock b/packages/twenty-apps/internal/twenty-meeting-bot/yarn.lock index 2dc1d33658..c5ce1d0aca 100644 --- a/packages/twenty-apps/internal/twenty-meeting-bot/yarn.lock +++ b/packages/twenty-apps/internal/twenty-meeting-bot/yarn.lock @@ -1250,6 +1250,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:3.2.6": version: 3.2.6 resolution: "@vitest/expect@npm:3.2.6" @@ -3143,6 +3224,7 @@ __metadata: "@sniptt/guards": "npm:^0.2.0" "@types/node": "npm:^24.7.2" "@types/react": "npm:^19.0.0" + "@typescript/native-preview": "npm:^7.0.0-dev.20260116.1" oxlint: "npm:^0.16.0" react: "npm:^19.0.0" react-dom: "npm:^19.0.0"