mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
refactor(desktop): extract project-dir + workspace settings IPC from main.cjs into project-dir-ipc.cjs
Sixth main.cjs cluster peel. The hermes:setting:defaultProjectDir:get/set/pick
handlers + hermes:workspace:sanitize move verbatim into
electron/project-dir-ipc.cjs behind a registerProjectDirIpc({ ipcMain,
readDefaultProjectDir, writeDefaultProjectDir, resolveHermesCwd,
sanitizeWorkspaceCwd }) registrar. The config readers/writers + cwd resolvers stay
in the main process and are injected.
Channel names unchanged → preload + renderer untouched. Adds
electron/project-dir-ipc.test.cjs (surface + set/sanitize behavior; get/pick touch
Electron app/dialog and are exercised in-app only).
This commit is contained in:
parent
0ed0c2d39f
commit
e167ed7bb1
3 changed files with 127 additions and 36 deletions
|
|
@ -61,6 +61,7 @@ const { registerFsIpc } = require('./fs-ipc.cjs')
|
|||
const { registerTerminalIpc } = require('./terminal-ipc.cjs')
|
||||
const { registerUpdatesIpc } = require('./updates-ipc.cjs')
|
||||
const { registerLogsIpc } = require('./logs-ipc.cjs')
|
||||
const { registerProjectDirIpc } = require('./project-dir-ipc.cjs')
|
||||
const { OFFICIAL_REPO_HTTPS_URL, isOfficialSshRemote } = require('./update-remote.cjs')
|
||||
const { resolveBehindCount, shouldCountCommits } = require('./update-count.cjs')
|
||||
const { runRebuildWithRetry } = require('./update-rebuild.cjs')
|
||||
|
|
@ -6663,42 +6664,14 @@ ipcMain.handle('hermes:openPreviewInBrowser', async (_event, url) => {
|
|||
// settings mount and seeds the value into the picker; writing back persists
|
||||
// it via writeDefaultProjectDir so resolveHermesCwd picks it up on the next
|
||||
// session spawn (no app restart needed).
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:get', async () => ({
|
||||
dir: readDefaultProjectDir(),
|
||||
defaultLabel: app.getPath('home'),
|
||||
resolvedCwd: resolveHermesCwd()
|
||||
}))
|
||||
|
||||
ipcMain.handle('hermes:workspace:sanitize', async (_event, cwd) => sanitizeWorkspaceCwd(cwd))
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {
|
||||
const next = typeof dir === 'string' && dir.trim() ? dir.trim() : null
|
||||
|
||||
if (next) {
|
||||
try {
|
||||
fs.mkdirSync(next, { recursive: true })
|
||||
} catch (error) {
|
||||
throw new Error(`Could not create directory: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
writeDefaultProjectDir(next)
|
||||
|
||||
return { dir: next }
|
||||
})
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:pick', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: 'Choose default project directory',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
defaultPath: readDefaultProjectDir() || app.getPath('home')
|
||||
})
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return { canceled: true, dir: null }
|
||||
}
|
||||
|
||||
return { canceled: false, dir: result.filePaths[0] }
|
||||
// Default-project-dir + workspace settings IPC lives in project-dir-ipc.cjs;
|
||||
// config readers/writers + cwd resolvers are injected.
|
||||
registerProjectDirIpc({
|
||||
ipcMain,
|
||||
readDefaultProjectDir,
|
||||
resolveHermesCwd,
|
||||
sanitizeWorkspaceCwd,
|
||||
writeDefaultProjectDir
|
||||
})
|
||||
|
||||
ipcMain.handle('hermes:fetchLinkTitle', (_event, url) => fetchLinkTitle(url))
|
||||
|
|
|
|||
55
apps/desktop/electron/project-dir-ipc.cjs
Normal file
55
apps/desktop/electron/project-dir-ipc.cjs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
'use strict'
|
||||
|
||||
const { app, dialog } = require('electron')
|
||||
const fs = require('fs')
|
||||
|
||||
// Default-project-directory + workspace-cwd settings IPC: read / write / native
|
||||
// directory picker, plus workspace-cwd sanitize. The config readers/writers and
|
||||
// cwd resolvers live in the main process and are injected.
|
||||
function registerProjectDirIpc({
|
||||
ipcMain,
|
||||
readDefaultProjectDir,
|
||||
resolveHermesCwd,
|
||||
sanitizeWorkspaceCwd,
|
||||
writeDefaultProjectDir
|
||||
}) {
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:get', async () => ({
|
||||
dir: readDefaultProjectDir(),
|
||||
defaultLabel: app.getPath('home'),
|
||||
resolvedCwd: resolveHermesCwd()
|
||||
}))
|
||||
|
||||
ipcMain.handle('hermes:workspace:sanitize', async (_event, cwd) => sanitizeWorkspaceCwd(cwd))
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {
|
||||
const next = typeof dir === 'string' && dir.trim() ? dir.trim() : null
|
||||
|
||||
if (next) {
|
||||
try {
|
||||
fs.mkdirSync(next, { recursive: true })
|
||||
} catch (error) {
|
||||
throw new Error(`Could not create directory: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
writeDefaultProjectDir(next)
|
||||
|
||||
return { dir: next }
|
||||
})
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:pick', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: 'Choose default project directory',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
defaultPath: readDefaultProjectDir() || app.getPath('home')
|
||||
})
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return { canceled: true, dir: null }
|
||||
}
|
||||
|
||||
return { canceled: false, dir: result.filePaths[0] }
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { registerProjectDirIpc }
|
||||
63
apps/desktop/electron/project-dir-ipc.test.cjs
Normal file
63
apps/desktop/electron/project-dir-ipc.test.cjs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
'use strict'
|
||||
|
||||
const assert = require('node:assert/strict')
|
||||
const test = require('node:test')
|
||||
|
||||
const { registerProjectDirIpc } = require('./project-dir-ipc.cjs')
|
||||
|
||||
function fakeIpcMain() {
|
||||
const handlers = new Map()
|
||||
|
||||
return {
|
||||
handlers,
|
||||
handle(channel, handler) {
|
||||
assert.ok(!handlers.has(channel), `duplicate registration for ${channel}`)
|
||||
handlers.set(channel, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deps(overrides = {}) {
|
||||
return {
|
||||
readDefaultProjectDir: () => '/projects',
|
||||
resolveHermesCwd: () => '/cwd',
|
||||
sanitizeWorkspaceCwd: cwd => `safe:${cwd}`,
|
||||
writeDefaultProjectDir: () => {},
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
test('registerProjectDirIpc wires the project-dir + workspace settings channels', () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
|
||||
registerProjectDirIpc({ ipcMain, ...deps() })
|
||||
|
||||
assert.deepEqual([...ipcMain.handlers.keys()].sort(), [
|
||||
'hermes:setting:defaultProjectDir:get',
|
||||
'hermes:setting:defaultProjectDir:pick',
|
||||
'hermes:setting:defaultProjectDir:set',
|
||||
'hermes:workspace:sanitize'
|
||||
])
|
||||
})
|
||||
|
||||
// `get` / `pick` touch Electron's `app` / `dialog`, which are unavailable under
|
||||
// `node --test` (require('electron') is a path stub), so they're exercised in-app
|
||||
// only. The wiring of all four channels is covered by the surface test above.
|
||||
|
||||
test('set normalizes a blank dir to null and persists that (clears the override)', async () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
const writes = []
|
||||
|
||||
registerProjectDirIpc({ ipcMain, ...deps({ writeDefaultProjectDir: d => writes.push(d) }) })
|
||||
|
||||
assert.deepEqual(await ipcMain.handlers.get('hermes:setting:defaultProjectDir:set')({}, ' '), { dir: null })
|
||||
assert.deepEqual(writes, [null])
|
||||
})
|
||||
|
||||
test('workspace:sanitize delegates to the injected sanitizer', async () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
|
||||
registerProjectDirIpc({ ipcMain, ...deps() })
|
||||
|
||||
assert.equal(await ipcMain.handlers.get('hermes:workspace:sanitize')({}, '/x'), 'safe:/x')
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue