Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import config from '@egor.xyz/eslint-config';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{
// ignore files and folders from root: node_modules, dist, build, *.js, *.ts
ignores: ['node_modules', 'dist', 'build', '*.js', '*.ts', '.tmp', '.vite']
},
{
extends: config,
files: ['**/*.{ts,tsx}']
}
);
4,567 changes: 2,256 additions & 2,311 deletions package-lock.json

Large diffs are not rendered by default.

75 changes: 38 additions & 37 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
"description": "Your Daily GitHub Desktop Assistant",
"main": ".vite/build/app.js",
"scripts": {
"start": "electron-forge start",
"dev": "electron-forge start",
"package:arm": "electron-forge package --arch=arm64",
"make": "electron-forge make --arch=x64,arm64",
"make:arm": "NODE_ENV=production electron-forge make --arch=arm64",
"publish": "electron-forge publish --arch=x64,arm64 --platform=darwin",
"eslint": "eslint --ext .ts,.tsx . --fix",
"lint": "eslint --ext .ts,.tsx . --fix",
"debug": "electron-forge start --vscode"
},
"keywords": [],
Expand All @@ -23,55 +23,56 @@
"homepage": "https://devkitty.app/",
"license": "MIT",
"dependencies": {
"@blueprintjs/core": "^5.16.0",
"@blueprintjs/select": "^5.3.5",
"@blueprintjs/core": "^5.19.1",
"@blueprintjs/select": "^5.3.21",
"clsx": "^2.1.1",
"electron-log": "5.2.3",
"electron-store": "^10.0.0",
"get-installed-apps": "^1.1.0",
"electron-log": "^5.4.1",
"electron-store": "^10.1.0",
"get-installed-apps": "^1.1.1",
"lodash": "^4.17.21",
"octokit": "^4.0.2",
"octokit": "^4.1.4",
"polished": "^4.3.1",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router": "^7.0.1",
"simple-git": "^3.27.0",
"styled-components": "^6.1.13",
"update-electron-app": "^3.0.0",
"uuid": "^11.0.3",
"zustand": "^5.0.1"
"react-icons": "^5.5.0",
"react-router": "^7.6.2",
"simple-git": "^3.28.0",
"styled-components": "^6.1.19",
"update-electron-app": "^3.1.1",
"uuid": "^11.1.0",
"zustand": "^5.0.5"
},
"devDependencies": {
"@egor.xyz/eslint-config": "3.0.0",
"@electron-forge/cli": "^7.6.0",
"@electron-forge/maker-deb": "^7.6.0",
"@electron-forge/maker-dmg": "^7.6.0",
"@electron-forge/maker-rpm": "^7.6.0",
"@electron-forge/maker-squirrel": "^7.6.0",
"@electron-forge/maker-zip": "^7.6.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.6.0",
"@electron-forge/plugin-fuses": "^7.6.0",
"@electron-forge/plugin-vite": "^7.6.0",
"@electron-forge/publisher-github": "^7.6.0",
"@egor.xyz/eslint-config": "^3.5.0",
"@electron-forge/cli": "^7.8.1",
"@electron-forge/maker-deb": "^7.8.1",
"@electron-forge/maker-dmg": "^7.8.1",
"@electron-forge/maker-rpm": "^7.8.1",
"@electron-forge/maker-squirrel": "^7.8.1",
"@electron-forge/maker-zip": "^7.8.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
"@electron-forge/plugin-fuses": "^7.8.1",
"@electron-forge/plugin-vite": "^7.8.1",
"@electron-forge/publisher-github": "^7.8.1",
"@electron/fuses": "^1.8.0",
"@types/lodash": "^4.17.13",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@eslint/eslintrc": "^3.3.1",
"@types/lodash": "^4.17.19",
"@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7",
"@types/styled-components": "^5.1.34",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-react": "^4.6.0",
"babel-plugin-styled-components": "^2.1.4",
"dotenv": "^16.4.5",
"electron": "^33.2.1",
"electron-devtools-installer": "^3.2.0",
"dotenv": "^16.5.0",
"electron": "^33.4.11",
"electron-devtools-installer": "^3.2.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.2",
"vite": "^6.0.1",
"vite-plugin-checker": "^0.8.0",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-checker": "^0.9.3",
"vite-plugin-svgr": "^4.3.0",
"vite-tsconfig-paths": "^5.1.3"
"vite-tsconfig-paths": "^5.1.4"
}
}
3 changes: 1 addition & 2 deletions src/main/app.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { app, BrowserWindow, nativeTheme, shell } from 'electron';
import path from 'path';

import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
import log from 'electron-log';
import path from 'path';
import { updateElectronApp } from 'update-electron-app';

import './ipcs';
Expand Down
3 changes: 1 addition & 2 deletions src/main/ipcs/ipcDarkMode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ipcMain, nativeTheme } from 'electron';

import { ThemeSource } from 'types/Modal';
import { type ThemeSource } from 'types/Modal';

import { settings } from '../settings';

Expand Down
10 changes: 5 additions & 5 deletions src/main/ipcs/ipcGit.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ipcMain } from 'electron';

import { CleanOptions, ResetMode } from 'simple-git';

import { GitStatus } from 'types/project';
import { type GitStatus } from 'types/project';

import { getGit } from '../libs/git';

Expand All @@ -21,7 +19,7 @@ ipcMain.handle('git:getStatus', async (_, id: string): Promise<GitStatus> => {
try {
const origin = await git.remote(['get-url', 'origin']);
organization = origin ? origin.split(':')[1].split('/')[0] : undefined;
} catch (e) {
} catch {
/* empty */
}

Expand Down Expand Up @@ -67,7 +65,9 @@ ipcMain.handle('git:reset', async (_, id: string, target, force) => {

await git.clean(CleanOptions.FORCE);

force && (await git.push(['-f']));
if (force) {
await git.push(['-f']);
}

return { message: 'Project reset', success: true };
} catch (e) {
Expand Down
4 changes: 1 addition & 3 deletions src/main/ipcs/ipcGitHub.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { ipcMain, safeStorage } from 'electron';

import log from 'electron-log';
import { Octokit } from 'octokit';

import { PullType } from 'types/gitHub';
import { type PullType } from 'types/gitHub';

import { getRepoInfo } from '../libs/git';
import { settings } from '../settings';
Expand Down
8 changes: 4 additions & 4 deletions src/main/ipcs/ipcLaunch.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ipcMain } from 'electron';
import { type LaunchEditor } from 'types/foundEditor';
import { type LaunchShell } from 'types/foundShell';

import { LaunchEditor } from 'types/foundEditor';
import { LaunchShell } from 'types/foundShell';

import { launch } from '../libs/integrations/shellsLaunch';
import { launchExternalEditor } from '../libs/integrations/editrosLaunch';
import { launch } from '../libs/integrations/shellsLaunch';

ipcMain.handle('launch:editor', async (e, launchEditor: LaunchEditor) => {
launchExternalEditor(launchEditor.fullPath, launchEditor.editor);
});

ipcMain.handle('launch:shell', async (e, launchShell: LaunchShell) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
launch(launchShell.shell as any, launchShell.fullPath);
});
8 changes: 2 additions & 6 deletions src/main/ipcs/ipcProjects.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { dialog, ipcMain } from 'electron';

import { type Projects } from 'types/project';
import { v5 } from 'uuid';

import { Projects } from 'types/project';

import { settings } from '../settings';

ipcMain.handle('projects:get', () => {
return settings.get('projects');
});
ipcMain.handle('projects:get', () => settings.get('projects'));

ipcMain.handle('projects:add', async () => {
const res = await dialog.showOpenDialog({ properties: ['openDirectory', 'multiSelections'] });
Expand Down
5 changes: 2 additions & 3 deletions src/main/ipcs/ipcSticker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ipcMain, Menu, Tray, nativeImage, BrowserWindow } from 'electron';
import log from 'electron-log';
import { ipcMain, nativeImage, Tray } from 'electron';

let tray: Tray | null = null;
let tray: null | Tray = null;

ipcMain.handle('sticker:add', async (_, text: string): Promise<void> => {
if (tray) tray.destroy();
Expand Down
17 changes: 8 additions & 9 deletions src/main/ipcs/preload.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';

import { ThemeSource } from 'types/Modal';
import { AppSettings } from 'types/appSettings';
import { FoundEditor } from 'types/foundEditor';
import { FoundShell } from 'types/foundShell';
import { pullTypes } from 'types/gitHub';
import { GitStatus, Project } from 'types/project';
import { Settings } from 'types/settings';
import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron';
import { type AppSettings } from 'types/appSettings';
import { type FoundEditor } from 'types/foundEditor';
import { type FoundShell } from 'types/foundShell';
import { type pullTypes } from 'types/gitHub';
import { type ThemeSource } from 'types/Modal';
import { type GitStatus, type Project } from 'types/project';
import { type Settings } from 'types/settings';

const bridge = {
darkMode: {
Expand Down
4 changes: 1 addition & 3 deletions src/main/libs/childProcess.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { exec } from 'child_process';

export const execAsync = (command: string) => {
return new Promise((resolve, reject) => {
export const execAsync = (command: string) => new Promise((resolve, reject) => {
exec(command, (error, stdout) => {
if (error) {
reject(error);
Expand All @@ -10,4 +9,3 @@ export const execAsync = (command: string) => {
resolve(stdout.trim());
});
});
};
4 changes: 3 additions & 1 deletion src/main/libs/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { simpleGit } from 'simple-git';
import { settings } from '../settings';

export const getGit = async (id: string) => {
// @ts-ignore tmp
const projects = settings.get('projects');
// @ts-ignore tmp
const project = projects.find((project) => project.id === id);

if (!project) new Error('Project not found');
Expand All @@ -28,7 +30,7 @@ export const getRepoInfo = async (id: string) => {
owner: repository.split('/')[0],
repo: repository.split('/')[1]
};
} catch (_) {
} catch {
return {};
}
};
4 changes: 2 additions & 2 deletions src/main/libs/integrations/editrosLaunch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { spawn, SpawnOptions } from 'child_process';
import { spawn, type SpawnOptions } from 'child_process';

import { FoundEditor, ExternalEditorError } from '../../../types/foundEditor';
import { ExternalEditorError, type FoundEditor } from '../../../types/foundEditor';
import { pathExists } from './path-exists';

const darwin = true;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Hard-coded darwin flag breaks Windows / Linux support

const darwin = true; forces macOS logic on every platform.

-const darwin = true;
+const darwin = process.platform === 'darwin';

Without this, non-mac users will hit the open command path or incorrect error labels.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const darwin = true;
- const darwin = true;
+ const darwin = process.platform === 'darwin';
🤖 Prompt for AI Agents
In src/main/libs/integrations/editrosLaunch.ts at line 6, the constant darwin is
hard-coded to true, which forces macOS-specific logic on all platforms and
breaks Windows/Linux support. Replace the hard-coded true value with a dynamic
check that sets darwin to true only if the current platform is macOS, typically
by using process.platform === 'darwin'. This will ensure platform-specific code
paths execute correctly based on the actual OS.

Expand Down
22 changes: 11 additions & 11 deletions src/main/libs/integrations/fatal-error.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
/** Throw an error. */
export function fatalError(msg: string): never {
throw new Error(msg);
}

/**
* Utility function used to achieve exhaustive type checks at compile time.
*
Expand All @@ -25,22 +20,27 @@ export function assertNever(x: never, message: string): never {
* this will throw. The message should contain the rationale for knowing the
* value is defined.
*/
export function forceUnwrap<T>(message: string, x: T | null | undefined): T {
if (x == null) {
export function assertNonNullable<T>(x: T, message: string): asserts x is NonNullable<T> {
if (x === null) {
return fatalError(message);
} else {
return x;
}
}
Comment on lines +23 to 27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical bug: Incomplete null/undefined checking in assertion function.

The function documentation states it handles values that "could be null or undefined", but the implementation only checks for null with strict equality (=== null). This means undefined values will pass through without being caught, violating the type assertion contract.

Apply this fix to check for both null and undefined:

-  if (x === null) {
+  if (x == null) {
     return fatalError(message);
   }

Alternatively, use explicit checks for both:

-  if (x === null) {
+  if (x === null || x === undefined) {
     return fatalError(message);
   }
🤖 Prompt for AI Agents
In src/main/libs/integrations/fatal-error.ts around lines 23 to 27, the
assertNonNullable function only checks if x is strictly null but does not check
for undefined, which contradicts its intended behavior of asserting non-nullable
values. Update the condition to check if x is either null or undefined using a
strict equality check for both or a combined check (e.g., x === null || x ===
undefined) before calling fatalError to ensure the assertion correctly handles
both cases.


/** Throw an error. */
export function fatalError(msg: string): never {
throw new Error(msg);
}

/**
* Unwrap a value that, according to the type system, could be null or
* undefined, but which we know is not. If the value _is_ null or undefined,
* this will throw. The message should contain the rationale for knowing the
* value is defined.
*/
export function assertNonNullable<T>(x: T, message: string): asserts x is NonNullable<T> {
if (x == null) {
export function forceUnwrap<T>(message: string, x: null | T | undefined): T {
if (x === null) {
return fatalError(message);
} else {
return x;
}
}
14 changes: 5 additions & 9 deletions src/main/libs/integrations/getInstalledApps.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { exec } from 'child_process';

const getAppsSubDirectory = (stdout: string, directory: string): Array<string> => {
const getAppsSubDirectory = (stdout: string, directory: string): string[] => {
let stdoutArr = stdout.split(/[(\r\n)\r\n]+/);
stdoutArr = stdoutArr
.filter((o: any) => o)
.map((i: any) => {
return `${directory}/${i}`;
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
stdoutArr = stdoutArr.filter((o: any) => o).map((i: any) => `${directory}/${i}`);
return stdoutArr;
};

const getDirectoryContents = (directory: string): Promise<Array<string>> => {
return new Promise((resolve, reject) => {
const getDirectoryContents = (directory: string): Promise<string[]> =>
new Promise((resolve, reject) => {
exec(`ls ${directory}`, (error, stdout) => {
if (error) {
reject(error);
Expand All @@ -24,7 +21,6 @@ const getDirectoryContents = (directory: string): Promise<Array<string>> => {
}
});
});
};

export const getInstalledApps = async (directory: string): Promise<string[]> => {
try {
Expand Down
10 changes: 4 additions & 6 deletions src/main/libs/integrations/integrations.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { BrowserWindow } from 'electron';
import os from 'os';

import { type BrowserWindow } from 'electron';
import { isEqual } from 'lodash';
import os from 'os';
import { type FoundShell } from 'types/foundShell';

import { FoundShell } from 'types/foundShell';

import { FoundEditor } from '../../../types/foundEditor';
import { type FoundEditor } from '../../../types/foundEditor';
import { settings } from '../../settings';
import { editorNames } from './editors';
import { getInstalledApps } from './getInstalledApps';
Expand Down
1 change: 0 additions & 1 deletion src/main/libs/integrations/path-exists.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { access } from 'fs/promises';

import { constant } from 'lodash';

/**
Expand Down
4 changes: 4 additions & 0 deletions src/main/libs/integrations/shells.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ const shells: Shell[] = [
{
bundleIdentifiers: ['com.github.wez.wezterm'],
name: 'WezTerm'
},
{
bundleIdentifiers: ['com.mitchellh.ghostty'],
name: 'Ghostty'
}
];

Expand Down
Loading