test: stop shelling out to ripgrep in the icon-export guard

`tests/primitives-exports.spec.ts` collected its file list with
`execFileSync('rg', …)`. ripgrep is installed on a developer machine but not
on the CI runners, so the whole spec failed to COLLECT there
(`spawnSync rg ENOENT`, which is why `ci` and `ci-windows` were red on this
branch while `pnpm test` was green locally). Replaced with a plain readdir
walk over `src/` and `tests/` — no external binary, and the scan is a few
hundred small files.

Verified by running the spec with a failing `rg` shim first on PATH: it
passes, which it cannot do while still calling ripgrep.
This commit is contained in:
Menghuan1918
2026-09-23 22:36:05 +08:00
parent 6805ab51a2
commit 819e0f2228
+29 -5
View File
@@ -15,13 +15,39 @@
* bundle through a re-export rather than a direct import.
*/
import { describe, expect, it } from 'vitest'
import { readFileSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { readFileSync, readdirSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
/** The specifier whose named exports the plugin depends on. */
const PACKAGE = '@deepseek-ai/dsh-client-ui-primitives'
const ROOT = resolve(fileURLToPath(new URL('..', import.meta.url)))
/**
* Every TypeScript source under the given roots.
*
* Deliberately a plain readdir walk rather than a ripgrep call: this spec used
* to shell out to `rg`, which exists on a developer machine but NOT on the CI
* runners, so the whole file failed to COLLECT there (`spawnSync rg ENOENT`)
* while passing locally. The scan is a few hundred small files.
* @param roots - directories relative to the repository root.
* @returns absolute file paths, sorted for a stable failure output.
*/
function sourceFiles(roots: readonly string[]): string[] {
const found: string[] = []
const walk = (dir: string): void => {
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const path = join(dir, entry.name)
if (entry.isDirectory()) walk(path)
else if (/\.tsx?$/.test(entry.name)) found.push(path)
}
}
for (const root of roots) walk(resolve(ROOT, root))
return found.sort()
}
/**
* Every value symbol the repository imports from the primitives package.
* Type-only imports are ignored: they are erased before the bundle is built
@@ -29,10 +55,8 @@ const PACKAGE = '@deepseek-ai/dsh-client-ui-primitives'
* @returns the imported symbol names, deduplicated.
*/
function importedSymbols(): string[] {
const files = execFileSync('rg', ['-l', PACKAGE, 'src', 'tests'], { encoding: 'utf8' })
.trim().split('\n').filter(Boolean)
const names = new Set<string>()
for (const file of files) {
for (const file of sourceFiles(['src', 'tests'])) {
const source = readFileSync(file, 'utf8')
for (const match of source.matchAll(
new RegExp(`import\\s+(type\\s+)?\\{([^}]*)\\}\\s+from\\s+['"]${PACKAGE}['"]`, 'g'),