From 6e9b2560a334f4df9d96d46e6bc14d385f0ad602 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Tue, 18 Aug 2026 12:25:09 +0800 Subject: [PATCH] fix(locale): keep the test-runtime devDependency and narrow the parity gate catch Restore @deepseek-ai/dsh-client-test-runtime in ui-settings-general: the package still imports bindSnapshotSelector from it in tests/components.client.spec.tsx, so removing it was manifest drift. The earlier knip report predated that file arriving on this branch. Swallow only ENOENT when reading a directory in the parity gate. A broad catch treated EACCES or an I/O failure as "absent", which would narrow the sweep and let the gate pass while checking less. --- .../client/ui-settings-general/package.json | 1 + pnpm-lock.yaml | 3 ++ scripts/locale-dictionary-parity.spec.ts | 37 +++++++++---------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/client/ui-settings-general/package.json b/packages/client/ui-settings-general/package.json index 14e826630e..59a7694633 100644 --- a/packages/client/ui-settings-general/package.json +++ b/packages/client/ui-settings-general/package.json @@ -67,6 +67,7 @@ "@deepseek-ai/dsh-client-connection": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-runtime": "workspace:^", + "@deepseek-ai/dsh-client-test-runtime": "workspace:^", "@deepseek-ai/dsh-client-ui-primitives": "workspace:^", "@deepseek-ai/dsh-client-ui-settings": "workspace:^", "@deepseek-ai/dsh-client-ui-sidebar": "workspace:^", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 154a76c92f..ce67604757 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2490,6 +2490,9 @@ importers: '@deepseek-ai/dsh-client-runtime': specifier: workspace:^ version: link:../runtime + '@deepseek-ai/dsh-client-test-runtime': + specifier: workspace:^ + version: link:../../test-support/client-runtime '@deepseek-ai/dsh-client-ui-primitives': specifier: workspace:^ version: link:../ui-primitives diff --git a/scripts/locale-dictionary-parity.spec.ts b/scripts/locale-dictionary-parity.spec.ts index ea40919fd9..5284801ae4 100644 --- a/scripts/locale-dictionary-parity.spec.ts +++ b/scripts/locale-dictionary-parity.spec.ts @@ -20,7 +20,7 @@ */ import type { Dirent } from 'node:fs' -import { existsSync, readdirSync, readFileSync } from 'node:fs' +import { readdirSync, readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import ts from 'typescript' @@ -47,30 +47,27 @@ function sourceFiles(): string[] { /** Immediate subdirectory names, or none when the path is not a directory. */ function directories(dir: string): string[] { - if (!existsSync(dir)) return [] - let entries: Dirent[] + return readEntries(dir).filter(entry => entry.isDirectory()).map(entry => entry.name) +} + +/** + * Directory entries, treating only a genuinely absent directory as empty. + * Any other failure (`EACCES`, I/O) rethrows: silently reading it as "absent" + * would narrow the sweep and let the gate pass while checking less. + * @param dir - absolute directory path. + * @returns entries, or none when the directory does not exist. + */ +function readEntries(dir: string): Dirent[] { try { - entries = readdirSync(dir, { withFileTypes: true }) - } catch { - // Swallows only the race between existsSync and readdirSync (a package - // directory removed mid-sweep); readdirSync is the sole statement in the - // try, so no other failure can reach here. - return [] + return readdirSync(dir, { withFileTypes: true }) + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return [] + throw error } - return entries.filter(entry => entry.isDirectory()).map(entry => entry.name) } function walk(dir: string, out: string[]): void { - if (!existsSync(dir)) return - let entries: Dirent[] - try { - entries = readdirSync(dir, { withFileTypes: true }) - } catch { - // Same narrow race as `directories`: readdirSync is the only statement - // guarded, so this cannot mask a parse or assertion failure. - return - } - for (const entry of entries) { + for (const entry of readEntries(dir)) { const full = resolve(dir, entry.name) if (entry.isDirectory()) walk(full, out) else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) out.push(full)