Compare commits

...
12 Commits
Author SHA1 Message Date
semantic-release-bot 7a6587cb8b chore(release): 0.15.3
## [0.15.3](https://github.com/evandrodevbr/GeminiNexus/compare/v0.15.2...v0.15.3) (2026-05-02)

### 👷 Build System

* allow fs-xattr and appdmg to build in pnpm v10 ([1c26a9e](https://github.com/evandrodevbr/GeminiNexus/commit/1c26a9e542f3fa5a05b370b8a332ffe5a727ea92))
2026-05-02 05:13:21 +00:00
Gemini Nexus Bot e1c9a9e34a Merge branch 'main' of https://github.com/evandrodevbr/GeminiNexus 2026-05-02 02:12:25 -03:00
Gemini Nexus Bot 1c26a9e542 build: allow fs-xattr and appdmg to build in pnpm v10 2026-05-02 02:09:53 -03:00
semantic-release-bot 5026be12d2 chore(release): 0.15.2
## [0.15.2](https://github.com/evandrodevbr/GeminiNexus/compare/v0.15.1...v0.15.2) (2026-05-02)

### 👷 Build System

* allow macos-alias and other native modules to build in pnpm v10 ([2e3a65b](https://github.com/evandrodevbr/GeminiNexus/commit/2e3a65b76b91012638c422f6223b79399775c635))
2026-05-02 04:57:50 +00:00
Gemini Nexus Bot c05daba98b Merge branch 'main' of https://github.com/evandrodevbr/GeminiNexus 2026-05-02 01:57:07 -03:00
Gemini Nexus Bot 2e3a65b76b build: allow macos-alias and other native modules to build in pnpm v10
- Fixes MakerDMG failure (Cannot find module '../build/Release/volume.node')
- Add macos-alias, better-sqlite3, keytar, and electron to trustedDependencies and pnpm.onlyBuiltDependencies
- Also updated pnpm-workspace.yaml allowBuilds directive
2026-05-02 01:48:15 -03:00
semantic-release-bot 62634b5511 chore(release): 0.15.1
## [0.15.1](https://github.com/evandrodevbr/GeminiNexus/compare/v0.15.0...v0.15.1) (2026-05-02)

### ✅ Tests

* add universal electron mock to fix headless CI test runs ([1d35801](https://github.com/evandrodevbr/GeminiNexus/commit/1d35801980e3e54cec1d3e52ed75850eb7eec350))
* fix test environments failing due to missing IS_DEV_ENVIRONMENT mock export ([269ad18](https://github.com/evandrodevbr/GeminiNexus/commit/269ad18ed6c09585e881d0d916558b1bcc7ad940))
* update proxy-advanced-handler tests to reflect optional service registration ([f4daaa8](https://github.com/evandrodevbr/GeminiNexus/commit/f4daaa8ca13bc75c7e5090905176c06b61ecfedc))

### 👷 Build System

* replace MakerSquirrel with MakerZIP for Windows ([27223f5](https://github.com/evandrodevbr/GeminiNexus/commit/27223f587d7facf25beef77aa6984cd248f2703d))
2026-05-02 04:46:37 +00:00
Gemini Nexus Bot 27223f587d build: replace MakerSquirrel with MakerZIP for Windows
- Remove @electron-forge/maker-squirrel dependency
- Squirrel.Windows fails on modern Windows GitHub Actions runners with Win32Exception during zip creation
- We already use MakerWix which produces a much better .msi installer for Windows users
- Add MakerZIP support for win32 to provide a portable alternative alongside the MSI
2026-05-02 01:42:46 -03:00
Gemini Nexus Bot 1d35801980 test: add universal electron mock to fix headless CI test runs
Vitest workers throw 'Electron failed to install correctly' when backend unit tests indirectly import electron via path/util modules. This mock provides stubs for ipc, app, and BrowserWindow interfaces so happy-dom environments don't crash expecting native binaries.
2026-05-02 01:27:57 -03:00
Gemini Nexus Bot 269ad18ed6 test: fix test environments failing due to missing IS_DEV_ENVIRONMENT mock export 2026-05-02 01:18:12 -03:00
Gemini Nexus Bot 9b9b977ae6 Merge branch 'main' of https://github.com/evandrodevbr/GeminiNexus 2026-05-02 01:09:55 -03:00
Gemini Nexus Bot f4daaa8ca1 test: update proxy-advanced-handler tests to reflect optional service registration
- Mock service-registry correctly exposing getServiceOptional
- Update tests to expect successful fallback resolutions when services are not yet registered
- Remove obsolete error test that is now correctly handled via ConfigManager fallback
2026-05-02 01:05:06 -03:00
17 changed files with 3152 additions and 8700 deletions

No files matched your search

+24
View File
@@ -2,6 +2,30 @@
# Changelog # Changelog
## [0.15.3](https://github.com/evandrodevbr/GeminiNexus/compare/v0.15.2...v0.15.3) (2026-05-02)
### 👷 Build System
* allow fs-xattr and appdmg to build in pnpm v10 ([1c26a9e](https://github.com/evandrodevbr/GeminiNexus/commit/1c26a9e542f3fa5a05b370b8a332ffe5a727ea92))
## [0.15.2](https://github.com/evandrodevbr/GeminiNexus/compare/v0.15.1...v0.15.2) (2026-05-02)
### 👷 Build System
* allow macos-alias and other native modules to build in pnpm v10 ([2e3a65b](https://github.com/evandrodevbr/GeminiNexus/commit/2e3a65b76b91012638c422f6223b79399775c635))
## [0.15.1](https://github.com/evandrodevbr/GeminiNexus/compare/v0.15.0...v0.15.1) (2026-05-02)
### ✅ Tests
* add universal electron mock to fix headless CI test runs ([1d35801](https://github.com/evandrodevbr/GeminiNexus/commit/1d35801980e3e54cec1d3e52ed75850eb7eec350))
* fix test environments failing due to missing IS_DEV_ENVIRONMENT mock export ([269ad18](https://github.com/evandrodevbr/GeminiNexus/commit/269ad18ed6c09585e881d0d916558b1bcc7ad940))
* update proxy-advanced-handler tests to reflect optional service registration ([f4daaa8](https://github.com/evandrodevbr/GeminiNexus/commit/f4daaa8ca13bc75c7e5090905176c06b61ecfedc))
### 👷 Build System
* replace MakerSquirrel with MakerZIP for Windows ([27223f5](https://github.com/evandrodevbr/GeminiNexus/commit/27223f587d7facf25beef77aa6984cd248f2703d))
## [0.15.0](https://github.com/evandrodevbr/GeminiNexus/compare/v0.14.0...v0.15.0) (2026-05-02) ## [0.15.0](https://github.com/evandrodevbr/GeminiNexus/compare/v0.14.0...v0.15.0) (2026-05-02)
### ✨ Features ### ✨ Features
+1 -6
View File
@@ -10,7 +10,6 @@ import type {
import { MakerDeb } from '@electron-forge/maker-deb'; import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerDMG } from '@electron-forge/maker-dmg'; import { MakerDMG } from '@electron-forge/maker-dmg';
import { MakerRpm } from '@electron-forge/maker-rpm'; import { MakerRpm } from '@electron-forge/maker-rpm';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerWix } from '@electron-forge/maker-wix'; import { MakerWix } from '@electron-forge/maker-wix';
import { MakerZIP } from '@electron-forge/maker-zip'; import { MakerZIP } from '@electron-forge/maker-zip';
import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives'; import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
@@ -441,10 +440,6 @@ const config: ForgeConfig = {
}, },
}, },
makers: [ makers: [
new MakerSquirrel({
setupIcon: 'images/icon.ico',
iconUrl: 'https://raw.githubusercontent.com/evandrodevbr/GeminiNexus/main/images/icon.ico',
}),
...(process.platform === 'win32' && process.arch === 'x64' && isWixAvailable() ...(process.platform === 'win32' && process.arch === 'x64' && isWixAvailable()
? [ ? [
new MakerWix({ new MakerWix({
@@ -463,7 +458,7 @@ const config: ForgeConfig = {
}, },
['darwin'], ['darwin'],
), ),
new MakerZIP({}, ['darwin']), new MakerZIP({}, ['darwin', 'win32']),
appImageMaker, appImageMaker,
new MakerRpm({}), new MakerRpm({}),
new MakerDeb({}), new MakerDeb({}),
+25 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "gemini-nexus", "name": "gemini-nexus",
"productName": "Gemini Nexus", "productName": "Gemini Nexus",
"version": "0.15.0", "version": "0.15.3",
"description": "Gemini Nexus - Electron App", "description": "Gemini Nexus - Electron App",
"main": ".vite/build/main.js", "main": ".vite/build/main.js",
"private": true, "private": true,
@@ -32,7 +32,6 @@
"@electron-forge/maker-deb": "^7.11.1", "@electron-forge/maker-deb": "^7.11.1",
"@electron-forge/maker-dmg": "^7.11.1", "@electron-forge/maker-dmg": "^7.11.1",
"@electron-forge/maker-rpm": "^7.11.1", "@electron-forge/maker-rpm": "^7.11.1",
"@electron-forge/maker-squirrel": "^7.11.1",
"@electron-forge/maker-wix": "^7.9.0", "@electron-forge/maker-wix": "^7.9.0",
"@electron-forge/maker-zip": "^7.11.1", "@electron-forge/maker-zip": "^7.11.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.11.1", "@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
@@ -171,9 +170,32 @@
"@nestjs/core", "@nestjs/core",
"@sentry/cli", "@sentry/cli",
"@swc/core", "@swc/core",
"protobufjs" "protobufjs",
"macos-alias",
"better-sqlite3",
"keytar",
"electron",
"electron-winstaller",
"esbuild",
"fs-xattr",
"appdmg"
], ],
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [
"@bitdisaster/exe-icon-extractor",
"@nestjs/core",
"@sentry/cli",
"@swc/core",
"protobufjs",
"macos-alias",
"better-sqlite3",
"keytar",
"electron",
"electron-winstaller",
"esbuild",
"fs-xattr",
"appdmg"
],
"overrides": { "overrides": {
"node-abi": "^4.28.0", "node-abi": "^4.28.0",
"fastify": "^5.8.5", "fastify": "^5.8.5",
+2986 -8662
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -8,5 +8,8 @@ allowBuilds:
electron-winstaller: true electron-winstaller: true
esbuild: true esbuild: true
keytar: true keytar: true
macos-alias: true
protobufjs: true protobufjs: true
sharp: true sharp: true
fs-xattr: true
appdmg: true
+1
View File
@@ -17,6 +17,7 @@ vi.mock('../../utils/paths', async () => {
const path = await import('path'); const path = await import('path');
const agentDir = path.join(process.cwd(), 'temp_test_agent'); const agentDir = path.join(process.cwd(), 'temp_test_agent');
return { return {
IS_DEV_ENVIRONMENT: false,
getAgentDir: vi.fn(() => agentDir), getAgentDir: vi.fn(() => agentDir),
getAccountsFilePath: vi.fn(() => path.join(agentDir, 'accounts.json')), getAccountsFilePath: vi.fn(() => path.join(agentDir, 'accounts.json')),
getBackupsDir: vi.fn(() => path.join(agentDir, 'backups')), getBackupsDir: vi.fn(() => path.join(agentDir, 'backups')),
+1
View File
@@ -58,6 +58,7 @@ vi.mock('../../ipc/database/dbConnection', () => ({
})); }));
vi.mock('../../utils/paths', () => ({ vi.mock('../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getGeminiNexusDbPaths: () => ['mock-db'], getGeminiNexusDbPaths: () => ['mock-db'],
getCloudAccountsDbPath: () => 'mock-cloud-db', getCloudAccountsDbPath: () => 'mock-cloud-db',
getAgentDir: vi.fn(() => '/mock/agent/dir'), getAgentDir: vi.fn(() => '/mock/agent/dir'),
@@ -22,6 +22,7 @@ vi.mock('../../ipc/database/dbConnection', () => ({
})); }));
vi.mock('../../utils/paths', () => ({ vi.mock('../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getGeminiNexusDbPaths: () => ['mock-db'], getGeminiNexusDbPaths: () => ['mock-db'],
})); }));
+1
View File
@@ -48,6 +48,7 @@ vi.mock('../../utils/logger', () => ({
})); }));
vi.mock('../../utils/paths', () => ({ vi.mock('../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getGeminiNexusDbPaths: vi.fn().mockReturnValue(['/tmp/test.vscdb']), getGeminiNexusDbPaths: vi.fn().mockReturnValue(['/tmp/test.vscdb']),
})); }));
+1
View File
@@ -65,6 +65,7 @@ vi.mock('../../../ipc/database/dbConnection', () => ({
})); }));
vi.mock('../../../utils/paths', () => ({ vi.mock('../../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getCloudAccountsDbPath: vi.fn(() => '/mock/db/path.sqlite'), getCloudAccountsDbPath: vi.fn(() => '/mock/db/path.sqlite'),
})); }));
@@ -49,6 +49,7 @@ vi.mock('../../../utils/logger', () => ({
})); }));
vi.mock('../../../utils/paths', () => ({ vi.mock('../../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getGeminiNexusDbPaths: vi.fn().mockReturnValue(['/tmp/test.vscdb']), getGeminiNexusDbPaths: vi.fn().mockReturnValue(['/tmp/test.vscdb']),
})); }));
@@ -53,6 +53,7 @@ vi.mock('../../../utils/logger', () => ({
})); }));
vi.mock('../../../utils/paths', () => ({ vi.mock('../../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getGeminiNexusStoragePaths: vi.fn().mockReturnValue(['/tmp/storage.json']), getGeminiNexusStoragePaths: vi.fn().mockReturnValue(['/tmp/storage.json']),
getGeminiNexusDbPaths: vi.fn().mockReturnValue(['/tmp/state.vscdb']), getGeminiNexusDbPaths: vi.fn().mockReturnValue(['/tmp/state.vscdb']),
getAgentDir: vi.fn().mockReturnValue('/tmp/agent'), getAgentDir: vi.fn().mockReturnValue('/tmp/agent'),
@@ -48,14 +48,19 @@ vi.mock('../../../ipc/proxy-advanced/service-registry', () => {
function getServiceOrThrow(key: string) { function getServiceOrThrow(key: string) {
const service = (proxyAdvancedRegistry as Record<string, unknown>)[key]; const service = (proxyAdvancedRegistry as Record<string, unknown>)[key];
if (!service) { if (!service) {
throw new Error(`${key} is not registered`); throw new Error(`ProxyAdvanced service '${key}' is not registered`);
} }
return service; return service;
} }
function getServiceOptional(key: string) {
return (proxyAdvancedRegistry as Record<string, unknown>)[key];
}
return { return {
proxyAdvancedRegistry, proxyAdvancedRegistry,
getServiceOrThrow, getServiceOrThrow,
getServiceOptional,
}; };
}); });
@@ -176,10 +181,10 @@ describe('Proxy Advanced Handler', () => {
}); });
describe('getParityCounters', () => { describe('getParityCounters', () => {
it('should return error when tokenManager is null', async () => { it('should return empty counters when tokenManager is null', async () => {
const result = await getParityCounters(); const result = await getParityCounters();
expect(result.success).toBe(false); expect(result.success).toBe(true);
expect(result.error).toBe('Proxy advanced service not initialized'); expect(result.data?.totalRequests).toBe(0);
}); });
it('should return counters from tokenManager', async () => { it('should return counters from tokenManager', async () => {
@@ -200,10 +205,10 @@ describe('Proxy Advanced Handler', () => {
}); });
describe('getCircuitBreakerStatus', () => { describe('getCircuitBreakerStatus', () => {
it('should return error when tokenManager is null', async () => { it('should return empty status when tokenManager is null', async () => {
const result = await getCircuitBreakerStatus(); const result = await getCircuitBreakerStatus();
expect(result.success).toBe(false); expect(result.success).toBe(true);
expect(result.error).toBe('Proxy advanced service not initialized'); expect(result.data?.states).toEqual({});
}); });
it('should return mapped status from tokenManager', async () => { it('should return mapped status from tokenManager', async () => {
@@ -222,10 +227,10 @@ describe('Proxy Advanced Handler', () => {
}); });
describe('getProxyMetrics', () => { describe('getProxyMetrics', () => {
it('should return error when proxyMetrics is null', async () => { it('should return zero metrics when proxyMetrics is null', async () => {
const result = await getProxyMetrics(); const result = await getProxyMetrics();
expect(result.success).toBe(false); expect(result.success).toBe(true);
expect(result.error).toBe('Proxy advanced service not initialized'); expect(result.data?.totalRequests).toBe(0);
}); });
it('should return metrics from proxyMetrics service', async () => { it('should return metrics from proxyMetrics service', async () => {
@@ -249,10 +254,10 @@ describe('Proxy Advanced Handler', () => {
}); });
describe('getRecentRequests', () => { describe('getRecentRequests', () => {
it('should return error when proxyReplay is null', async () => { it('should return empty array when proxyReplay is null', async () => {
const result = await getRecentRequests(); const result = await getRecentRequests();
expect(result.success).toBe(false); expect(result.success).toBe(true);
expect(result.error).toBe('Proxy advanced service not initialized'); expect(result.data).toEqual([]);
}); });
it('should return recent requests from proxyReplay', async () => { it('should return recent requests from proxyReplay', async () => {
@@ -410,10 +415,10 @@ describe('Proxy Advanced Handler', () => {
}); });
describe('generateIdeConfig', () => { describe('generateIdeConfig', () => {
it('should return error when proxyIdeConfig is null', async () => { it('should generate fallback config when proxyIdeConfig is null', async () => {
const result = await generateIdeConfig('vscode'); const result = await generateIdeConfig('vscode');
expect(result.success).toBe(false); expect(result.success).toBe(true);
expect(result.error).toBe('Proxy advanced service not initialized'); expect(result.data?.name).toBe('VS Code');
}); });
it('should return error for unsupported IDE', async () => { it('should return error for unsupported IDE', async () => {
@@ -428,7 +433,8 @@ describe('Proxy Advanced Handler', () => {
vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = { vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = {
generateConfigForCurrentServer: vi.fn().mockReturnValue({ generateConfigForCurrentServer: vi.fn().mockReturnValue({
ide: 'vscode', ide: 'vscode',
content: null, format: 'json',
content: { 'openai.url': 'http://localhost:8045/v1', 'openai.apiKey': 'key' },
}), }),
} as any; } as any;
@@ -443,7 +449,8 @@ describe('Proxy Advanced Handler', () => {
vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = { vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = {
generateConfigForCurrentServer: vi.fn().mockReturnValue({ generateConfigForCurrentServer: vi.fn().mockReturnValue({
ide: 'cursor', ide: 'cursor',
content: null, format: 'json',
content: { OPENAI_BASE_URL: 'http://localhost:8045/v1', OPENAI_API_KEY: 'key' },
}), }),
} as any; } as any;
@@ -453,16 +460,5 @@ describe('Proxy Advanced Handler', () => {
expect(result.data?.proxySetting).toBe('OPENAI_BASE_URL'); expect(result.data?.proxySetting).toBe('OPENAI_BASE_URL');
vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = null; vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = null;
}); });
it('should handle null result from config generator', async () => {
vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = {
generateConfigForCurrentServer: vi.fn().mockReturnValue(null),
} as any;
const result = await generateIdeConfig('vscode');
expect(result.success).toBe(false);
expect(result.error).toBe('Failed to generate IDE config: server not configured');
vi.mocked(proxyAdvancedRegistry).proxyIdeConfig = null;
});
}); });
}); });
@@ -25,6 +25,7 @@ vi.mock('../../../ipc/database/cloudHandler', () => ({
})); }));
vi.mock('../../../utils/paths', () => ({ vi.mock('../../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getCloudAccountsDbPath: vi.fn(() => '/mock/db/path.sqlite'), getCloudAccountsDbPath: vi.fn(() => '/mock/db/path.sqlite'),
})); }));
+1
View File
@@ -5,6 +5,7 @@ import path from 'path';
vi.mock('../../utils/paths', async () => { vi.mock('../../utils/paths', async () => {
const path = await import('path'); const path = await import('path');
return { return {
IS_DEV_ENVIRONMENT: false,
getAgentDir: vi.fn(() => path.join(process.cwd(), 'temp_test_logs')), getAgentDir: vi.fn(() => path.join(process.cwd(), 'temp_test_logs')),
}; };
}); });
+78
View File
@@ -59,3 +59,81 @@ class MockIntersectionObserver {
disconnect() {} disconnect() {}
} }
(globalThis as any).IntersectionObserver = MockIntersectionObserver; (globalThis as any).IntersectionObserver = MockIntersectionObserver;
// Universal Electron mock for testing environments
vi.mock('electron', () => {
return {
app: {
getPath: vi.fn((name) => `/mock/path/${name}`),
getName: vi.fn(() => 'Gemini Nexus Test'),
getVersion: vi.fn(() => '1.0.0'),
isPackaged: false,
getLocale: vi.fn(() => 'en-US'),
getPreferredSystemLanguages: vi.fn(() => ['en-US']),
requestSingleInstanceLock: vi.fn(() => true),
quit: vi.fn(),
disableHardwareAcceleration: vi.fn(),
setName: vi.fn(),
commandLine: {
appendSwitch: vi.fn(),
},
},
ipcMain: {
on: vi.fn(),
handle: vi.fn(),
removeHandler: vi.fn(),
removeAllListeners: vi.fn(),
},
ipcRenderer: {
on: vi.fn(),
off: vi.fn(),
once: vi.fn(),
invoke: vi.fn(),
send: vi.fn(),
postMessage: vi.fn(),
},
BrowserWindow: vi.fn().mockImplementation(() => ({
loadURL: vi.fn(),
loadFile: vi.fn(),
show: vi.fn(),
close: vi.fn(),
destroy: vi.fn(),
webContents: {
send: vi.fn(),
on: vi.fn(),
session: {
webRequest: {
onBeforeSendHeaders: vi.fn(),
}
}
},
on: vi.fn(),
})),
dialog: {
showMessageBox: vi.fn().mockResolvedValue({ response: 0 }),
showOpenDialog: vi.fn().mockResolvedValue({ canceled: true, filePaths: [] }),
showSaveDialog: vi.fn().mockResolvedValue({ canceled: true, filePath: '' }),
},
shell: {
openPath: vi.fn(),
openExternal: vi.fn(),
},
contextBridge: {
exposeInMainWorld: vi.fn(),
},
Tray: vi.fn().mockImplementation(() => ({
setToolTip: vi.fn(),
setContextMenu: vi.fn(),
on: vi.fn(),
destroy: vi.fn(),
})),
Menu: {
buildFromTemplate: vi.fn(),
},
nativeTheme: {
themeSource: 'system',
shouldUseDarkColors: true,
on: vi.fn(),
},
};
});
+1
View File
@@ -26,6 +26,7 @@ vi.mock('../../ipc/database/cloudHandler', () => ({
})); }));
vi.mock('../../utils/paths', () => ({ vi.mock('../../utils/paths', () => ({
IS_DEV_ENVIRONMENT: false,
getCloudAccountsDbPath: vi.fn(() => '/mock/db/path.sqlite'), getCloudAccountsDbPath: vi.fn(() => '/mock/db/path.sqlite'),
})); }));