From eceab7b5e8f3362b2e96eb82926ba54f5de2d545 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 15:11:06 +1000 Subject: [PATCH 01/12] refactor(api): use @octokit/types REST schema type Signed-off-by: Adam Setch --- package.json | 1 + pnpm-lock.yaml | 8 ++++++++ src/renderer/typesGitHub.ts | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/package.json b/package.json index f47d43e72..f993c1084 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@electron/notarize": "3.1.1", "@graphql-codegen/cli": "6.1.0", "@graphql-codegen/schema-ast": "5.0.0", + "@octokit/openapi-types": "^27.0.0", "@parcel/watcher": "2.5.1", "@primer/css": "22.0.2", "@primer/octicons-react": "19.21.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44229f751..4a0008b23 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@graphql-codegen/schema-ast': specifier: 5.0.0 version: 5.0.0(graphql@16.12.0) + '@octokit/openapi-types': + specifier: ^27.0.0 + version: 27.0.0 '@parcel/watcher': specifier: 2.5.1 version: 2.5.1 @@ -1620,6 +1623,9 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + '@oddbird/popover-polyfill@0.5.2': resolution: {integrity: sha512-iFrvar5SOMtKFOSjYvs4z9UlLqDdJbMx0mgISLcPedv+g0ac5sgeETLGtipHCVIae6HJPclNEH5aCyD1RZaEHw==} @@ -7902,6 +7908,8 @@ snapshots: mkdirp: 1.0.4 rimraf: 3.0.2 + '@octokit/openapi-types@27.0.0': {} + '@oddbird/popover-polyfill@0.5.2': {} '@parcel/watcher-android-arm64@2.5.1': diff --git a/src/renderer/typesGitHub.ts b/src/renderer/typesGitHub.ts index 15f810b49..876882d16 100644 --- a/src/renderer/typesGitHub.ts +++ b/src/renderer/typesGitHub.ts @@ -1,3 +1,5 @@ +// import type { components } from '@octokit/openapi-types'; + import type { GitifyNotification, GitifySubject, Link } from './types'; // TODO: #828 Add explicit types for GitHub API response vs Gitify Notifications object @@ -46,6 +48,7 @@ export type UserType = | 'Organization' | 'User'; +// export type Notification = components['schemas']['thread']; export interface GitHubNotification { id: string; unread: boolean; @@ -119,6 +122,7 @@ export interface User { site_admin: boolean; } +// export type Repository = components['schemas']['repository']; export interface Repository { id: number; node_id: string; @@ -189,6 +193,7 @@ export interface Owner { site_admin: boolean; } +// export type Commit = components['schemas']['commit']; export interface Commit { sha: string; node_id: string; @@ -260,6 +265,7 @@ export interface CommitComment { body: string; } +// export type Release = components['schemas']['release']; export interface Release { url: Link; assets_url: Link; @@ -283,6 +289,8 @@ export interface GitHubRESTError { documentation_url: Link; } +// export type NotificationThreadSubscription = +// components['schemas']['thread-subscription']; export interface NotificationThreadSubscription { subscribed: boolean; ignored: boolean; From 8e536745e45a54b05293467568e1a9ca5979ee64 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 16:26:06 +1000 Subject: [PATCH 02/12] refactor: use fragments for details Signed-off-by: Adam Setch --- src/renderer/typesGitHub.ts | 285 +++--------------- .../utils/notifications/handlers/commit.ts | 19 +- .../utils/notifications/handlers/release.ts | 9 +- 3 files changed, 66 insertions(+), 247 deletions(-) diff --git a/src/renderer/typesGitHub.ts b/src/renderer/typesGitHub.ts index 876882d16..4886bd314 100644 --- a/src/renderer/typesGitHub.ts +++ b/src/renderer/typesGitHub.ts @@ -1,15 +1,22 @@ -// import type { components } from '@octokit/openapi-types'; +import type { components } from '@octokit/openapi-types'; import type { GitifyNotification, GitifySubject, Link } from './types'; // TODO: #828 Add explicit types for GitHub API response vs Gitify Notifications object -export type Notification = GitHubNotification & GitifyNotification; -export type Subject = GitHubSubject & GitifySubject; +export type Notification = GitHubNotification & + GitifyNotification & { + reason: Reason; + subject: Subject; + repository: Repository; + }; +export type Subject = GitHubSubject & { + url: Link; + latest_comment_url: Link; + type: SubjectType; +} & GitifySubject; /** - * * GitHub REST API Response Types - * **/ export type Reason = @@ -48,254 +55,48 @@ export type UserType = | 'Organization' | 'User'; -// export type Notification = components['schemas']['thread']; -export interface GitHubNotification { - id: string; - unread: boolean; - reason: Reason; - updated_at: string; - last_read_at: string | null; - subject: Subject; - repository: Repository; - url: Link; - subscription_url: Link; -} - -interface GitHubSubject { - title: string; - url: Link | null; - latest_comment_url: Link | null; - type: SubjectType; -} - -export type UserDetails = User & UserProfile; - -export interface UserProfile { - name: string; - company: string; - blog: string; - location: string; - email: string; - hireable: string; - bio: string; - twitter_username: string; - public_repos: number; - public_gists: number; - followers: number; - following: number; - created_at: string; - updated_at: string; - private_gists: number; - total_private_repos: number; - owned_private_repos: number; - disk_usage: number; - collaborators: number; - two_factor_authentication: boolean; - plan: Plan; -} - -export interface Plan { - name: string; - space: number; - private_repos: number; - collaborators: number; -} +export type GitHubNotification = components['schemas']['thread']; +export type GitHubSubject = components['schemas']['thread']['subject']; -export interface User { - login: string; - id: number; - node_id: string; - avatar_url: Link; - gravatar_url: Link; - url: Link; +export type Repository = components['schemas']['repository'] & { html_url: Link; - followers_url: Link; - following_url: Link; - gists_url: Link; - starred_url: Link; - subscriptions_url: Link; - organizations_url: Link; - repos_url: Link; - events_url: Link; - received_events_url: Link; - type: UserType; - site_admin: boolean; -} - -// export type Repository = components['schemas']['repository']; -export interface Repository { - id: number; - node_id: string; - name: string; - full_name: string; - private: boolean; owner: Owner; - html_url: Link; - description: string; - fork: boolean; - url: Link; - forks_url: Link; - keys_url: Link; - collaborators_url: Link; - teams_url: Link; - hooks_url: Link; - issue_events_url: Link; - events_url: Link; - assignees_url: Link; - branches_url: Link; - tags_url: Link; - blobs_url: Link; - git_tags_url: Link; - git_refs_url: Link; - trees_url: Link; - statuses_url: Link; - languages_url: Link; - stargazers_url: Link; - contributors_url: Link; - subscribers_url: Link; - subscription_url: Link; - commits_url: Link; - git_commits_url: Link; - comments_url: Link; - issue_comment_url: Link; - contents_url: Link; - compare_url: Link; - merges_url: Link; - archive_url: Link; - downloads_url: Link; - issues_url: Link; - pulls_url: Link; - milestones_url: Link; - notifications_url: Link; - labels_url: Link; - releases_url: Link; - deployments_url: Link; -} +}; -export interface Owner { - login: string; - id: number; - node_id: string; +export type Owner = NonNullable & { + type: UserType; avatar_url: Link; - gravatar_id: string; - url: Link; - html_url: Link; - followers_url: Link; - following_url: Link; - gists_url: Link; - starred_url: Link; - subscriptions_url: Link; - organizations_url: Link; - repos_url: Link; - events_url: Link; - received_events_url: Link; +}; +type BaseRepository = components['schemas']['repository']; + +export type Commit = Omit & { + author: BaseCommit['author'] extends null ? null : StrongCommitAuthor; +}; +type BaseCommit = components['schemas']['commit']; +type StrongCommitAuthor = NonNullable & { type: UserType; - site_admin: boolean; -} +}; -// export type Commit = components['schemas']['commit']; -export interface Commit { - sha: string; - node_id: string; - commit: { - author: CommitUser; - committer: CommitUser; - message: string; - tree: { - sha: string; - url: Link; - }; - url: Link; - comment_count: number; - verification: { - verified: boolean; - reason: string; - signature: string | null; - payload: string | null; - }; - }; - url: Link; - html_url: Link; - comments_url: Link; - author: User; - committer: User; - parents: CommitParent[]; - stats: { - total: number; - additions: number; - deletions: number; - }; - files: CommitFiles[]; -} - -interface CommitUser { - name: string; - email: string; - date: string; -} - -interface CommitParent { - sha: string; - url: Link; - html_url: Link; -} - -interface CommitFiles { - sha: string; - filename: string; - status: string; - additions: number; - deletions: number; - changes: number; - blob_url: Link; - raw_url: Link; - contents_url: Link; - patch: string; -} +export type CommitComment = Omit & { + user: BaseCommitComment['user'] extends null ? null : StrongCommitCommentUser; +}; +type BaseCommitComment = components['schemas']['commit-comment']; +type StrongCommitCommentUser = NonNullable & { + type: UserType; +}; -export interface CommitComment { - url: Link; - html_url: Link; - issue_url: Link; - id: number; - node_id: string; - user: User; - created_at: string; - updated_at: string; - body: string; -} +export type Release = Omit & { + author: BaseRelease['author'] extends null ? null : StrongReleaseAuthor; +}; +type BaseRelease = components['schemas']['release']; +type StrongReleaseAuthor = NonNullable & { + type: UserType; +}; -// export type Release = components['schemas']['release']; -export interface Release { - url: Link; - assets_url: Link; - upload_url: Link; - html_url: Link; - id: number; - author: User; - node_id: string; - tag_name: string; - target_commitish: string; - name: string | null; - body: string | null; - draft: boolean; - prerelease: boolean; - created_at: string; - published_at: string | null; -} +export type NotificationThreadSubscription = + components['schemas']['thread-subscription']; export interface GitHubRESTError { message: string; documentation_url: Link; } - -// export type NotificationThreadSubscription = -// components['schemas']['thread-subscription']; -export interface NotificationThreadSubscription { - subscribed: boolean; - ignored: boolean; - reason: string | null; - created_at: string; - url: Link; - thread_url: Link; -} diff --git a/src/renderer/utils/notifications/handlers/commit.ts b/src/renderer/utils/notifications/handlers/commit.ts index cc13f5bcd..37d5a90ff 100644 --- a/src/renderer/utils/notifications/handlers/commit.ts +++ b/src/renderer/utils/notifications/handlers/commit.ts @@ -5,10 +5,11 @@ import { GitCommitIcon } from '@primer/octicons-react'; import type { GitifyNotificationState, + GitifyNotificationUser, GitifySubject, SettingsState, } from '../../../types'; -import type { Notification, Subject, User } from '../../../typesGitHub'; +import type { Notification, Subject } from '../../../typesGitHub'; import { getCommit, getCommitComment } from '../../api/client'; import { isStateFilteredOut } from '../filters/filter'; import { DefaultHandler } from './default'; @@ -28,7 +29,7 @@ class CommitHandler extends DefaultHandler { return null; } - let user: User; + let user: GitifyNotificationUser; if (notification.subject.latest_comment_url) { const commitComment = ( @@ -38,13 +39,23 @@ class CommitHandler extends DefaultHandler { ) ).data; - user = commitComment.user; + user = { + login: commitComment.user.login, + html_url: commitComment.user.html_url, + avatar_url: commitComment.user.avatar_url, + type: commitComment.user.type, + }; } else { const commit = ( await getCommit(notification.subject.url, notification.account.token) ).data; - user = commit.author; + user = { + login: commit.author.login, + html_url: commit.author.html_url, + avatar_url: commit.author.avatar_url, + type: commit.author.type, + }; } return { diff --git a/src/renderer/utils/notifications/handlers/release.ts b/src/renderer/utils/notifications/handlers/release.ts index 9b2fe3149..ca4f4a1ec 100644 --- a/src/renderer/utils/notifications/handlers/release.ts +++ b/src/renderer/utils/notifications/handlers/release.ts @@ -35,7 +35,14 @@ class ReleaseHandler extends DefaultHandler { return { state: releaseState, - user: getNotificationAuthor([release.author]), + user: getNotificationAuthor([ + { + login: release.author.login, + html_url: release.author.html_url, + avatar_url: release.author.avatar_url, + type: release.author.type, + }, + ]), }; } From 08dd85a7a7853c1920db1a556b452e9b41a0dafb Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 16:33:25 +1000 Subject: [PATCH 03/12] Merge branch 'main' into refactor/api-user Signed-off-by: Adam Setch --- src/renderer/typesGitHub.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/renderer/typesGitHub.ts b/src/renderer/typesGitHub.ts index 4886bd314..25b33270a 100644 --- a/src/renderer/typesGitHub.ts +++ b/src/renderer/typesGitHub.ts @@ -3,6 +3,7 @@ import type { components } from '@octokit/openapi-types'; import type { GitifyNotification, GitifySubject, Link } from './types'; // TODO: #828 Add explicit types for GitHub API response vs Gitify Notifications object + export type Notification = GitHubNotification & GitifyNotification & { reason: Reason; @@ -15,10 +16,6 @@ export type Subject = GitHubSubject & { type: SubjectType; } & GitifySubject; -/** - * GitHub REST API Response Types - **/ - export type Reason = | 'approval_requested' | 'assign' From 85ae38db9ad2e630ab554d1b1840a1d9dfdf0410 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 21:11:21 +1000 Subject: [PATCH 04/12] refactor(api): use @octokit/types REST schema type Signed-off-by: Adam Setch --- src/renderer/typesGitHub.ts | 50 ++-- .../utils/api/__mocks__/response-mocks.ts | 255 +++--------------- 2 files changed, 51 insertions(+), 254 deletions(-) diff --git a/src/renderer/typesGitHub.ts b/src/renderer/typesGitHub.ts index a30d2fd61..0076cb9dd 100644 --- a/src/renderer/typesGitHub.ts +++ b/src/renderer/typesGitHub.ts @@ -4,39 +4,7 @@ import type { GitifyNotification, GitifySubject, Link } from './types'; // TODO: #828 Add explicit types for GitHub API response vs Gitify Notifications object -export interface User { - login: string; - id: number; - node_id: string; - avatar_url: Link; - gravatar_url: Link; - url: Link; - html_url: Link; - followers_url: Link; - following_url: Link; - gists_url: Link; - starred_url: Link; - subscriptions_url: Link; - organizations_url: Link; - repos_url: Link; - events_url: Link; - received_events_url: Link; - type: UserType; - site_admin: boolean; -} - -export type Notification = GitHubNotification & - GitifyNotification & { - reason: Reason; - subject: Subject; - repository: Repository; - }; -export type Subject = GitHubSubject & { - url: Link; - latest_comment_url: Link; - type: SubjectType; -} & GitifySubject; - +// Stronger typings for Reason string attribute export type Reason = | 'approval_requested' | 'assign' @@ -54,6 +22,7 @@ export type Reason = | 'subscribed' | 'team_mention'; +// Stronger typings for Subject Type string attribute export type SubjectType = | 'CheckSuite' | 'Commit' @@ -66,6 +35,7 @@ export type SubjectType = | 'RepositoryVulnerabilityAlert' | 'WorkflowRun'; +// Stronger typings for Reason User Type attribute export type UserType = | 'Bot' | 'EnterpriseUserAccount' @@ -73,6 +43,20 @@ export type UserType = | 'Organization' | 'User'; +export type Notification = GitHubNotification & + GitifyNotification & { + reason: Reason; + subject: Subject; + repository: Repository; + }; +export type Subject = GitHubSubject & { + url: Link; + latest_comment_url: Link; + type: SubjectType; +} & GitifySubject; + +export type User = components['schemas']['simple-user'] & { type: UserType }; + export type GitHubNotification = components['schemas']['thread']; export type GitHubSubject = components['schemas']['thread']['subject']; diff --git a/src/renderer/utils/api/__mocks__/response-mocks.ts b/src/renderer/utils/api/__mocks__/response-mocks.ts index dd2e8d4ad..6008cfd71 100644 --- a/src/renderer/utils/api/__mocks__/response-mocks.ts +++ b/src/renderer/utils/api/__mocks__/response-mocks.ts @@ -3,36 +3,35 @@ import { mockGitHubEnterpriseServerAccount, } from '../../../__mocks__/account-mocks'; import type { Link } from '../../../types'; -import type { Notification, Repository, User } from '../../../typesGitHub'; +import type { + Notification, + Owner, + Repository, + User, +} from '../../../typesGitHub'; -export const mockNotificationUser: User = { - login: 'octocat', +export const mockNotificationUser = { id: 123456789, - node_id: 'MDQ6VXNlcjE=', + login: 'octocat', avatar_url: 'https://avatars.githubusercontent.com/u/583231?v=4' as Link, - gravatar_url: '' as Link, url: 'https://api.github.com/users/octocat' as Link, html_url: 'https://github.com/octocat' as Link, - followers_url: 'https://api.github.com/users/octocat/followers' as Link, - following_url: - 'https://api.github.com/users/octocat/following{/other_user}' as Link, - gists_url: 'https://api.github.com/users/octocat/gists{/gist_id}' as Link, - starred_url: - 'https://api.github.com/users/octocat/starred{/owner}{/repo}' as Link, - subscriptions_url: - 'https://api.github.com/users/octocat/subscriptions' as Link, - organizations_url: 'https://api.github.com/users/octocat/orgs' as Link, - repos_url: 'https://api.github.com/users/octocat/repos' as Link, - events_url: 'https://api.github.com/users/octocat/events{/privacy}' as Link, - received_events_url: - 'https://api.github.com/users/octocat/received_events' as Link, type: 'User', - site_admin: false, -}; +} satisfies Partial; // 2 Notifications // Hostname : 'github.com' // Repository : 'gitify-app/notifications-test' +const mockGitHubOwner: Owner = { + id: 6333409, + login: 'gitify-app', + avatar_url: + 'https://avatars.githubusercontent.com/u/133795385?s=200&v=4' as Link, + url: 'https://api.github.com/users/gitify-app' as Link, + html_url: 'https://github.com/gitify-app' as Link, + type: 'User', +} as unknown as Owner; + export const mockGitHubNotifications: Notification[] = [ { account: mockGitHubCloudAccount, @@ -73,116 +72,12 @@ export const mockGitHubNotifications: Notification[] = [ }, repository: { id: 57216596, - node_id: 'MDEwOlJlcG9zaXRvcnkzNjAyOTcwNg==', name: 'notifications-test', full_name: 'gitify-app/notifications-test', url: 'https://api.github.com/gitify-app/notifications-test' as Link, - owner: { - login: 'gitify-app', - id: 6333409, - node_id: 'MDQ6VXNlcjYzMzM0MDk=', - avatar_url: - 'https://avatars.githubusercontent.com/u/133795385?s=200&v=4' as Link, - gravatar_id: '', - url: 'https://api.github.com/users/gitify-app' as Link, - html_url: 'https://github.com/gitify-app' as Link, - followers_url: - 'https://api.github.com/users/gitify-app/followers' as Link, - following_url: - 'https://api.github.com/users/gitify-app/following{/other_user}' as Link, - gists_url: - 'https://api.github.com/users/gitify-app/gists{/gist_id}' as Link, - starred_url: - 'https://api.github.com/users/gitify-app/starred{/owner}{/repo}' as Link, - subscriptions_url: - 'https://api.github.com/users/gitify-app/subscriptions' as Link, - organizations_url: - 'https://api.github.com/users/gitify-app/orgs' as Link, - repos_url: 'https://api.github.com/users/gitify-app/repos' as Link, - events_url: - 'https://api.github.com/users/gitify-app/events{/privacy}' as Link, - received_events_url: - 'https://api.github.com/users/gitify-app/received_events' as Link, - type: 'User', - site_admin: false, - }, - private: true, - description: 'Test Repository', - fork: false, - archive_url: - 'https://api.github.com/repos/gitify-app/notifications-test/{archive_format}{/ref}' as Link, - assignees_url: - 'https://api.github.com/repos/gitify-app/notifications-test/assignees{/user}' as Link, - blobs_url: - 'https://api.github.com/repos/gitify-app/notifications-test/git/blobs{/sha}' as Link, - branches_url: - 'https://api.github.com/repos/gitify-app/notifications-test/branches{/branch}' as Link, - collaborators_url: - 'https://api.github.com/repos/gitify-app/notifications-test/collaborators{/collaborator}' as Link, - comments_url: - 'https://api.github.com/repos/gitify-app/notifications-test/comments{/number}' as Link, - commits_url: - 'https://api.github.com/repos/gitify-app/notifications-test/commits{/sha}' as Link, - compare_url: - 'https://api.github.com/repos/gitify-app/notifications-test/compare/{base}...{head}' as Link, - contents_url: - 'https://api.github.com/repos/gitify-app/notifications-test/contents/{+path}' as Link, - contributors_url: - 'https://api.github.com/repos/gitify-app/notifications-test/contributors' as Link, - deployments_url: - 'https://api.github.com/repos/gitify-app/notifications-test/deployments' as Link, - downloads_url: - 'https://api.github.com/repos/gitify-app/notifications-test/downloads' as Link, - events_url: - 'https://api.github.com/repos/gitify-app/notifications-test/events' as Link, - forks_url: - 'https://api.github.com/repos/gitify-app/notifications-test/forks' as Link, - git_commits_url: - 'https://api.github.com/repos/gitify-app/notifications-test/git/commits{/sha}' as Link, - git_refs_url: - 'https://api.github.com/repos/gitify-app/notifications-test/git/refs{/sha}' as Link, - git_tags_url: - 'https://api.github.com/repos/gitify-app/notifications-test/git/tags{/sha}' as Link, - hooks_url: - 'https://api.github.com/repos/gitify-app/notifications-test/hooks' as Link, + owner: mockGitHubOwner, html_url: 'https://github.com/gitify-app/notifications-test' as Link, - issue_comment_url: - 'https://api.github.com/repos/gitify-app/notifications-test/issues/comments{/number}' as Link, - issue_events_url: - 'https://api.github.com/repos/gitify-app/notifications-test/issues/events{/number}' as Link, - issues_url: - 'https://api.github.com/repos/gitify-app/notifications-test/issues{/number}' as Link, - keys_url: - 'https://api.github.com/repos/gitify-app/notifications-test/keys{/key_id}' as Link, - labels_url: - 'https://api.github.com/repos/gitify-app/notifications-test/labels{/name}' as Link, - languages_url: - 'https://api.github.com/repos/gitify-app/notifications-test/languages' as Link, - merges_url: - 'https://api.github.com/repos/gitify-app/notifications-test/merges' as Link, - milestones_url: - 'https://api.github.com/repos/gitify-app/notifications-test/milestones{/number}' as Link, - notifications_url: - 'https://api.github.com/repos/gitify-app/notifications-test/notifications{?since,all,participating}' as Link, - pulls_url: - 'https://api.github.com/repos/gitify-app/notifications-test/pulls{/number}' as Link, - releases_url: - 'https://api.github.com/repos/gitify-app/notifications-test/releases{/id}' as Link, - stargazers_url: - 'https://api.github.com/repos/gitify-app/notifications-test/stargazers' as Link, - statuses_url: - 'https://api.github.com/repos/gitify-app/notifications-test/statuses/{sha}' as Link, - subscribers_url: - 'https://api.github.com/repos/gitify-app/notifications-test/subscribers' as Link, - subscription_url: - 'https://api.github.com/repos/gitify-app/notifications-test/subscription' as Link, - tags_url: - 'https://api.github.com/repos/gitify-app/notifications-test/tags' as Link, - teams_url: - 'https://api.github.com/repos/gitify-app/notifications-test/teams' as Link, - trees_url: - 'https://api.github.com/repos/gitify-app/notifications-test/git/trees{/sha}' as Link, - }, + } as unknown as Repository, url: 'https://api.github.com/notifications/threads/138661096' as Link, subscription_url: 'https://api.github.com/notifications/threads/138661096/subscription' as Link, @@ -207,39 +102,8 @@ export const mockGitHubNotifications: Notification[] = [ id: 57216596, name: 'notifications-test', full_name: 'gitify-app/notifications-test', - owner: { - login: 'gitify-app', - id: 6333409, - avatar_url: - 'https://avatars.githubusercontent.com/u/133795385?s=200&v=4' as Link, - gravatar_id: '', - url: 'https://api.github.com/users/gitify-app' as Link, - html_url: 'https://github.com/gitify-app' as Link, - followers_url: - 'https://api.github.com/users/gitify-app/followers' as Link, - following_url: - 'https://api.github.com/users/gitify-app/following{/other_user}' as Link, - gists_url: - 'https://api.github.com/users/gitify-app/gists{/gist_id}' as Link, - starred_url: - 'https://api.github.com/users/gitify-app/starred{/owner}{/repo}' as Link, - subscriptions_url: - 'https://api.github.com/users/gitify-app/subscriptions' as Link, - organizations_url: - 'https://api.github.com/users/gitify-app/orgs' as Link, - repos_url: 'https://api.github.com/users/gitify-app/repos' as Link, - events_url: - 'https://api.github.com/users/gitify-app/events{/privacy}' as Link, - received_events_url: - 'https://api.github.com/users/gitify-app/received_events' as Link, - type: 'User', - site_admin: false, - }, - private: true, - html_url: 'https://github.com/gitify-app/notifications-test', - description: null, - fork: false, - // Removed the rest of the properties + owner: mockGitHubOwner, + html_url: 'https://github.com/gitify-app/notifications-test' as Link, } as unknown as Repository, url: 'https://api.github.com/notifications/threads/148827438' as Link, subscription_url: @@ -250,6 +114,15 @@ export const mockGitHubNotifications: Notification[] = [ // 2 Notifications // Hostname : 'github.gitify.io' // Repository : 'myorg/notifications-test' +const mockEnterpriseOwner = { + login: 'myorg', + id: 4, + avatar_url: 'https://github.gitify.io/avatars/u/4?' as Link, + url: 'https://github.gitify.io/api/v3/users/myorg', + html_url: 'https://github.gitify.io/myorg' as Link, + type: 'Organization', +} as unknown as Owner; + export const mockEnterpriseNotifications: Notification[] = [ { account: mockGitHubEnterpriseServerAccount, @@ -271,38 +144,8 @@ export const mockEnterpriseNotifications: Notification[] = [ id: 1, name: 'notifications-test', full_name: 'myorg/notifications-test', - owner: { - login: 'myorg', - id: 4, - avatar_url: 'https://github.gitify.io/avatars/u/4?' as Link, - gravatar_id: '', - url: 'https://github.gitify.io/api/v3/users/myorg', - html_url: 'https://github.gitify.io/myorg' as Link, - followers_url: - 'https://github.gitify.io/api/v3/users/myorg/followers' as Link, - following_url: - 'https://github.gitify.io/api/v3/users/myorg/following{/other_user}' as Link, - gists_url: - 'https://github.gitify.io/api/v3/users/myorg/gists{/gist_id}' as Link, - starred_url: - 'https://github.gitify.io/api/v3/users/myorg/starred{/owner}{/repo}' as Link, - subscriptions_url: - 'https://github.gitify.io/api/v3/users/myorg/subscriptions' as Link, - organizations_url: - 'https://github.gitify.io/api/v3/users/myorg/orgs' as Link, - repos_url: 'https://github.gitify.io/api/v3/users/myorg/repos' as Link, - events_url: - 'https://github.gitify.io/api/v3/users/myorg/events{/privacy}' as Link, - received_events_url: - 'https://github.gitify.io/api/v3/users/myorg/received_events' as Link, - type: 'Organization', - site_admin: false, - }, - private: true, + owner: mockEnterpriseOwner, html_url: 'https://github.gitify.io/myorg/notifications-test' as Link, - description: null, - fork: false, - // Removed the rest of the properties } as unknown as Repository, url: 'https://github.gitify.io/api/v3/notifications/threads/4' as Link, subscription_url: @@ -328,38 +171,8 @@ export const mockEnterpriseNotifications: Notification[] = [ id: 1, name: 'notifications-test', full_name: 'myorg/notifications-test', - owner: { - login: 'myorg', - id: 4, - avatar_url: 'https://github.gitify.io/avatars/u/4?' as Link, - gravatar_id: '', - url: 'https://github.gitify.io/api/v3/users/myorg' as Link, - html_url: 'https://github.gitify.io/myorg' as Link, - followers_url: - 'https://github.gitify.io/api/v3/users/myorg/followers' as Link, - following_url: - 'https://github.gitify.io/api/v3/users/myorg/following{/other_user}' as Link, - gists_url: - 'https://github.gitify.io/api/v3/users/myorg/gists{/gist_id}' as Link, - starred_url: - 'https://github.gitify.io/api/v3/users/myorg/starred{/owner}{/repo}' as Link, - subscriptions_url: - 'https://github.gitify.io/api/v3/users/myorg/subscriptions' as Link, - organizations_url: - 'https://github.gitify.io/api/v3/users/myorg/orgs' as Link, - repos_url: 'https://github.gitify.io/api/v3/users/myorg/repos' as Link, - events_url: - 'https://github.gitify.io/api/v3/users/myorg/events{/privacy}' as Link, - received_events_url: - 'https://github.gitify.io/api/v3/users/myorg/received_events' as Link, - type: 'Organization', - site_admin: false, - }, - private: true, + owner: mockEnterpriseOwner, html_url: 'https://github.gitify.io/myorg/notifications-test' as Link, - description: null, - fork: false, - // Removed the rest of the properties } as unknown as Repository, url: 'https://github.gitify.io/api/v3/notifications/threads/3' as Link, subscription_url: From 1ea2d72371de45ace6bce2cfdb73306ee986f414 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 21:26:20 +1000 Subject: [PATCH 05/12] refactor(api): use @octokit/types REST schema type Signed-off-by: Adam Setch --- src/renderer/typesGitHub.ts | 83 +++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/src/renderer/typesGitHub.ts b/src/renderer/typesGitHub.ts index 0076cb9dd..d972bd9d7 100644 --- a/src/renderer/typesGitHub.ts +++ b/src/renderer/typesGitHub.ts @@ -4,7 +4,7 @@ import type { GitifyNotification, GitifySubject, Link } from './types'; // TODO: #828 Add explicit types for GitHub API response vs Gitify Notifications object -// Stronger typings for Reason string attribute +// Stronger typings for string literal attributes export type Reason = | 'approval_requested' | 'assign' @@ -22,7 +22,6 @@ export type Reason = | 'subscribed' | 'team_mention'; -// Stronger typings for Subject Type string attribute export type SubjectType = | 'CheckSuite' | 'Commit' @@ -35,7 +34,6 @@ export type SubjectType = | 'RepositoryVulnerabilityAlert' | 'WorkflowRun'; -// Stronger typings for Reason User Type attribute export type UserType = | 'Bot' | 'EnterpriseUserAccount' @@ -43,60 +41,75 @@ export type UserType = | 'Organization' | 'User'; -export type Notification = GitHubNotification & - GitifyNotification & { - reason: Reason; - subject: Subject; - repository: Repository; - }; -export type Subject = GitHubSubject & { +// Base types from Octokit +export type NotificationThreadSubscription = + components['schemas']['thread-subscription']; + +type BaseNotification = components['schemas']['thread']; +type BaseUser = components['schemas']['simple-user']; +type BaseRepository = components['schemas']['repository']; +type BaseCommit = components['schemas']['commit']; +type BaseCommitComment = components['schemas']['commit-comment']; +type BaseRelease = components['schemas']['release']; +type BaseSubject = components['schemas']['thread']['subject']; + +// Strengthen user-related types with explicit property overrides +type GitHubNotification = Omit< + BaseNotification, + 'reason' | 'subject' | 'repository' +> & { + reason: Reason; + subject: Subject; + repository: Repository; +}; + +type GitHubSubject = Omit< + BaseSubject, + 'url' | 'latest_comment_url' | 'type' +> & { url: Link; latest_comment_url: Link; type: SubjectType; -} & GitifySubject; +}; -export type User = components['schemas']['simple-user'] & { type: UserType }; +// Exported strengthened types +export type Notification = GitHubNotification & GitifyNotification; -export type GitHubNotification = components['schemas']['thread']; -export type GitHubSubject = components['schemas']['thread']['subject']; +export type Subject = GitHubSubject & GitifySubject; -export type Repository = components['schemas']['repository'] & { +export type Repository = Omit & { html_url: Link; owner: Owner; }; -export type Owner = NonNullable & { +export type User = Omit & { type: UserType }; + +export type Owner = Omit< + NonNullable, + 'type' | 'avatar_url' +> & { type: UserType; avatar_url: Link; }; -type BaseRepository = components['schemas']['repository']; +// Strengthen commit-related types export type Commit = Omit & { - author: BaseCommit['author'] extends null ? null : StrongCommitAuthor; -}; -type BaseCommit = components['schemas']['commit']; -type StrongCommitAuthor = NonNullable & { - type: UserType; + author: BaseCommit['author'] extends null + ? null + : NonNullable & { type: UserType }; }; export type CommitComment = Omit & { - user: BaseCommitComment['user'] extends null ? null : StrongCommitCommentUser; -}; -type BaseCommitComment = components['schemas']['commit-comment']; -type StrongCommitCommentUser = NonNullable & { - type: UserType; + user: BaseCommitComment['user'] extends null + ? null + : NonNullable & { type: UserType }; }; export type Release = Omit & { - author: BaseRelease['author'] extends null ? null : StrongReleaseAuthor; + author: BaseRelease['author'] extends null + ? null + : NonNullable & { type: UserType }; }; -type BaseRelease = components['schemas']['release']; -type StrongReleaseAuthor = NonNullable & { - type: UserType; -}; - -export type NotificationThreadSubscription = - components['schemas']['thread-subscription']; export interface GitHubRESTError { message: string; From 0acecacdfb5ac3757b7fdaec87b1c288b5f83395 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 21:28:50 +1000 Subject: [PATCH 06/12] refactor(api): use @octokit/types REST schema type Signed-off-by: Adam Setch --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f9ab2168c..3c39b15f6 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "@electron/notarize": "3.1.1", "@graphql-codegen/cli": "6.1.0", "@graphql-codegen/schema-ast": "5.0.0", - "@octokit/openapi-types": "^27.0.0", + "@octokit/openapi-types": "27.0.0", "@parcel/watcher": "2.5.1", "@primer/css": "22.0.2", "@primer/octicons-react": "19.21.1", @@ -148,4 +148,4 @@ "*": "biome check --fix --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests --updateSnapshot" } -} +} \ No newline at end of file From 6b4236d14b90db2a5121c97d4f7a2d9c4a64fc1b Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 21:34:19 +1000 Subject: [PATCH 07/12] refactor(api): use @octokit/types REST schema type Signed-off-by: Adam Setch --- package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3c39b15f6..6f9c4c233 100644 --- a/package.json +++ b/package.json @@ -148,4 +148,4 @@ "*": "biome check --fix --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests --updateSnapshot" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 920d4b5ce..636f76e84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: specifier: 5.0.0 version: 5.0.0(graphql@16.12.0) '@octokit/openapi-types': - specifier: ^27.0.0 + specifier: 27.0.0 version: 27.0.0 '@parcel/watcher': specifier: 2.5.1 From 81628e5a2d2d39345fac6dc01c37396d7602b475 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 25 Dec 2025 07:33:57 +1000 Subject: [PATCH 08/12] refactor(api): use @octokit/types REST schema type Signed-off-by: Adam Setch --- .../__snapshots__/Sidebar.test.tsx.snap | 42 +++++------- .../__snapshots__/AppLayout.test.tsx.snap | 22 +++---- .../AccountNotifications.test.tsx.snap | 64 ------------------- .../NotificationRow.test.tsx.snap | 24 ------- .../RepositoryNotifications.test.tsx.snap | 36 ----------- .../__snapshots__/Header.test.tsx.snap | 2 - .../__snapshots__/HoverButton.test.tsx.snap | 2 - .../SettingsFooter.test.tsx.snap | 2 +- .../__snapshots__/Filters.test.tsx.snap | 3 +- .../routes/__snapshots__/Login.test.tsx.snap | 6 -- .../LoginWithOAuthApp.test.tsx.snap | 12 +--- ...LoginWithPersonalAccessToken.test.tsx.snap | 12 +--- .../__snapshots__/Settings.test.tsx.snap | 20 +----- src/renderer/typesGitHub.ts | 11 ++-- .../utils/api/__mocks__/response-mocks.ts | 55 +++++++--------- 15 files changed, 60 insertions(+), 253 deletions(-) diff --git a/src/renderer/components/__snapshots__/Sidebar.test.tsx.snap b/src/renderer/components/__snapshots__/Sidebar.test.tsx.snap index 81e172b63..76c3cca09 100644 --- a/src/renderer/components/__snapshots__/Sidebar.test.tsx.snap +++ b/src/renderer/components/__snapshots__/Sidebar.test.tsx.snap @@ -2,7 +2,7 @@ exports[`renderer/components/Sidebar.tsx Filter notifications highlight filters sidebar if any are saved 1`] = `