fmt(js): npm run fix on merge (#68681)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
hermes-seaeye[bot] 2026-07-21 14:28:28 +00:00 committed by GitHub
parent d9dae17e97
commit d604141d09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 54 additions and 4 deletions

View file

@ -11,11 +11,13 @@ const tempDirs: string[] = []
function tempDir(): string {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'hermes-repo-scan-'))
tempDirs.push(dir)
return dir
}
function makeRepo(root: string, valid = true): void {
fs.mkdirSync(path.join(root, '.git'), { recursive: true })
if (valid) {
fs.writeFileSync(path.join(root, '.git', 'HEAD'), 'ref: refs/heads/main\n')
}
@ -23,6 +25,7 @@ function makeRepo(root: string, valid = true): void {
afterEach(() => {
vi.restoreAllMocks()
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { force: true, recursive: true })
}

View file

@ -36,11 +36,13 @@ export function normalizeRepoScanPath(rawPath: string, options: RepoScanPathOpti
const homeDir = options.homeDir ?? os.homedir()
const pathApi = pathApiFor(platform)
const raw = String(rawPath ?? '').trim()
if (!raw) {
return null
}
let expanded = raw
if (raw === '~') {
expanded = homeDir
} else if (raw.startsWith('~/') || raw.startsWith('~\\')) {
@ -50,6 +52,7 @@ export function normalizeRepoScanPath(rawPath: string, options: RepoScanPathOpti
const absolute = pathApi.isAbsolute(expanded) ? expanded : pathApi.resolve(homeDir, expanded)
const value = pathApi.normalize(absolute)
const key = platform === 'win32' ? value.toLocaleLowerCase('en-US') : value
return { key, value }
}
@ -58,10 +61,13 @@ export function repoScanPathIsWithin(candidate: string, parent: string, options:
const pathApi = pathApiFor(platform)
const candidatePath = normalizeRepoScanPath(candidate, options)
const parentPath = normalizeRepoScanPath(parent, options)
if (!candidatePath || !parentPath) {
return false
}
const relative = pathApi.relative(parentPath.key, candidatePath.key)
return (
relative === '' || (relative !== '..' && !relative.startsWith(`..${pathApi.sep}`) && !pathApi.isAbsolute(relative))
)
@ -95,6 +101,7 @@ export async function scanGitRepos(roots: string[], options: RepoScanOptions = {
const maxDepth = Number.isFinite(maxDepthValue) && maxDepthValue >= 0 ? maxDepthValue : DEFAULT_MAX_DEPTH
const pathOptions: RepoScanPathOptions = {}
const requestedRoots = Array.isArray(roots) && roots.length > 0 ? roots : [os.homedir()]
const searchRoots = [
...new Map(
requestedRoots
@ -103,9 +110,11 @@ export async function scanGitRepos(roots: string[], options: RepoScanOptions = {
.map(entry => [entry.key, entry.value])
).values()
]
const exclusions = (options.excludePaths ?? [])
.map(excluded => normalizeRepoScanPath(excluded, pathOptions))
.filter((entry): entry is NormalizedScanPath => entry !== null)
const found = new Map<string, { root: string; label: string }>()
function isExcluded(candidate: string): boolean {
@ -118,6 +127,7 @@ export async function scanGitRepos(roots: string[], options: RepoScanOptions = {
}
let entries: fs.Dirent[]
try {
entries = await fsp.readdir(dir, { withFileTypes: true })
} catch {
@ -125,28 +135,34 @@ export async function scanGitRepos(roots: string[], options: RepoScanOptions = {
}
const gitDir = entries.find(entry => entry.name === '.git' && entry.isDirectory())
if (gitDir) {
try {
await fsp.access(path.join(dir, '.git', 'HEAD'), fs.constants.R_OK)
} catch {
return
}
const normalized = normalizeRepoScanPath(dir, pathOptions)
if (normalized) {
found.set(normalized.key, {
root: normalized.value,
label: path.basename(normalized.value) || normalized.value
})
}
return
}
const subdirs = entries
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.') && !JUNK_DIRS.has(entry.name))
.map(entry => path.join(dir, entry.name))
await mapLimit(subdirs, MAX_CONCURRENCY, subdir => walk(subdir, depth + 1))
}
await mapLimit(searchRoots, MAX_CONCURRENCY, root => walk(root, 0))
return [...found.values()]
}

View file

@ -22,6 +22,7 @@ const { renameSession, request, activeGateway } = vi.hoisted(() => ({
request: vi.fn(async () => ({ title: 'rpc-title' }) as never),
activeGateway: vi.fn<() => { request: unknown } | null>(() => ({ request: undefined }))
}))
// Wire activeGateway's default return to the shared request mock now that it exists.
activeGateway.mockReturnValue({ request })

View file

@ -137,19 +137,23 @@ export function ConfigSettings({
void (async () => {
try {
const result = await saveHermesConfig(config)
if (!result.ok) {
throw new Error(c.autosaveFailed)
}
// Mirror the saved record into the shared cache so MCP/model surfaces
// reflect the edit without their own refetch.
setHermesConfigCache(config)
if (saveVersionRef.current === v) {
const discoverySignature = repoDiscoveryPolicySignature(repoDiscoveryPolicyFromConfig(config))
if (savedDiscoverySignatureRef.current !== discoverySignature) {
savedDiscoverySignatureRef.current = discoverySignature
await scanAndRecordRepos(true)
}
onConfigSaved?.()
}
} catch (err) {

View file

@ -231,6 +231,7 @@ export function setApiRequestProfile(profile: null | string): void {
function profileScoped(profile?: null | string): { profile?: string } {
const selected = profile === undefined ? _apiProfile : profile
return selected ? { profile: selected } : {}
}

View file

@ -90,10 +90,7 @@ if (
// Inverse: persisted/default active id is still the live-preview tab, but that
// target isn't open and file tabs are. Point at the first file tab so ⌘W and
// the strip agree before React's fallback sync runs.
if (
$rightRailActiveTabId.get() === RIGHT_RAIL_PREVIEW_TAB_ID &&
$filePreviewTabs.get().length > 0
) {
if ($rightRailActiveTabId.get() === RIGHT_RAIL_PREVIEW_TAB_ID && $filePreviewTabs.get().length > 0) {
selectRightRailTab($filePreviewTabs.get()[0]!.id)
}

View file

@ -292,6 +292,7 @@ describe('repository discovery policy', () => {
const gateway = { connectionState: 'open', request }
activeGateway.mockReturnValue(gateway as never)
gatewayAtom.set(gateway as never)
return gateway
}
@ -301,6 +302,7 @@ describe('repository discovery policy', () => {
? { active_id: null, projects: [], scoped_session_ids: [] }
: { accepted: false, repos: [] }
)
gatewayWith(request)
const scanRepos = vi.fn()
desktopGit.mockReturnValue({ scanRepos } as never)
@ -327,6 +329,7 @@ describe('repository discovery policy', () => {
? { active_id: null, projects: [], scoped_session_ids: [] }
: { accepted: true, repos: [] }
)
gatewayWith(request)
const scanRepos = vi.fn().mockResolvedValue([{ label: 'repo', root: '/work/repo' }])
desktopGit.mockReturnValue({ scanRepos } as never)
@ -370,10 +373,13 @@ describe('repository discovery policy', () => {
describe('project tree profile isolation', () => {
it('does not publish a late response from the previous profile', async () => {
let resolveA: ((value: unknown) => void) | undefined
const responseA = new Promise(resolve => {
resolveA = resolve
})
const gatewayA = { connectionState: 'open', request: vi.fn(() => responseA) }
const gatewayB = {
connectionState: 'open',
request: vi.fn().mockResolvedValue({
@ -382,6 +388,7 @@ describe('project tree profile isolation', () => {
scoped_session_ids: []
})
}
let current = gatewayA
activeGateway.mockImplementation(() => current as never)
gatewayAtom.set(gatewayA as never)

View file

@ -282,12 +282,15 @@ interface ActiveProjectsContext {
async function activeProjectsContext(): Promise<ActiveProjectsContext> {
const profile = $activeGatewayProfile.get() || 'default'
let gateway = activeGateway()
if (!gateway || gateway.connectionState !== 'open') {
gateway = await ensureActiveGatewayOpen()
}
if (!gateway || gateway !== activeGateway() || profile !== ($activeGatewayProfile.get() || 'default')) {
throw new Error('Active Hermes profile changed while connecting')
}
return { gateway, profile }
}
@ -318,13 +321,16 @@ let projectTreeRefreshGeneration = 0
async function refreshProjectTreeOn(gateway: HermesGateway): Promise<void> {
const generation = ++projectTreeRefreshGeneration
if (activeGateway() === gateway) {
$projectTreeLoading.set(true)
}
try {
const res = await gatewayRequestOn<ProjectTreePayload>(gateway, 'projects.tree', {
preview_limit: 3
})
if (generation !== projectTreeRefreshGeneration || activeGateway() !== gateway) {
return
}
@ -333,12 +339,15 @@ async function refreshProjectTreeOn(gateway: HermesGateway): Promise<void> {
$projectTree.set(res.projects ?? [])
$activeProjectId.set(res.active_id ?? null)
const tombstones = $removedSessionIds.get()
if (tombstones.size) {
const pending = new Set([...tombstones].filter(id => scoped.has(id)))
if (pending.size !== tombstones.size) {
$removedSessionIds.set(pending)
}
}
markProjectsRpcSuccess()
} catch (err) {
if (activeGateway() === gateway) {
@ -386,6 +395,7 @@ export interface RepoDiscoveryPolicy {
export function repoDiscoveryPolicyFromConfig(config: unknown): RepoDiscoveryPolicy {
const desktopValue = config && typeof config === 'object' ? (config as { desktop?: unknown }).desktop : undefined
const desktop =
desktopValue && typeof desktopValue === 'object'
? (desktopValue as {
@ -394,6 +404,7 @@ export function repoDiscoveryPolicyFromConfig(config: unknown): RepoDiscoveryPol
repo_scan_roots?: unknown
})
: {}
return {
enabled: desktop.repo_scan_enabled !== false,
roots: Array.isArray(desktop.repo_scan_roots)
@ -431,6 +442,7 @@ export async function scanAndRecordRepos(force = false): Promise<void> {
}
let context: ActiveProjectsContext
try {
context = await activeProjectsContext()
} catch {
@ -438,6 +450,7 @@ export async function scanAndRecordRepos(force = false): Promise<void> {
}
const scan = desktopGit()?.scanRepos
if (!scan) {
return
}
@ -449,12 +462,14 @@ export async function scanAndRecordRepos(force = false): Promise<void> {
try {
const policy = repoDiscoveryPolicyFromConfig(await getHermesConfig(context.profile))
const signature = repoDiscoveryPolicySignature(policy)
if (!force && (state.completedSignature === signature || state.runningSignature === signature)) {
return
}
generation = ++state.generation
state.runningSignature = signature
if (!policy.enabled) {
await gatewayRequestOn(context.gateway, 'projects.record_repos', {
discovery_policy: policy,
@ -463,13 +478,16 @@ export async function scanAndRecordRepos(force = false): Promise<void> {
} else {
scanningGatewayGenerations.set(context.gateway, generation)
syncReposScanning()
const repos = await scan(policy.roots, {
enabled: true,
excludePaths: policy.exclude_paths
})
if (state.generation !== generation) {
return
}
await gatewayRequestOn(context.gateway, 'projects.record_repos', {
discovery_policy: policy,
repos
@ -479,15 +497,18 @@ export async function scanAndRecordRepos(force = false): Promise<void> {
if (state.generation !== generation) {
return
}
state.completedSignature = signature
await refreshProjectTreeOn(context.gateway)
} catch {
state.completedSignature = undefined
} finally {
state.runningSignature = undefined
if (scanningGatewayGenerations.get(context.gateway) === generation) {
scanningGatewayGenerations.delete(context.gateway)
}
syncReposScanning()
}
}